odfdo
odfdo
Python library for OpenDocument format (ODF)

odfdo is a Python3 library implementing the ISO/IEC 26300 OpenDocument Format
standard.
Project: https://github.com/jdum/odfdo
Author: jerome.dumonteil@gmail.com
License: Apache License, Version 2.0
odfdo is a derivative work of the former lpod-python project.
Installation
Installation from Pypi (recommended):
pip install odfdo
Installation from sources (requiring setuptools):
pip install .
After installation from sources, you can check everything is working (some requirements: pytest, Pillow, ...):
pytest
The tests should run for a few seconds or minutes and issue no error.
Usage
from odfdo import Document, Paragraph
doc = Document('text')
doc.body.append(Paragraph("Hello world!"))
doc.save("hello.odt")
tl;dr
'Intended Audience :: Developers'
Documentation
There is no detailed documentation or tutorial, but:
- the
recipesfolder contains more than 50 working sample scripts, - the
docfolder contains an auto generated documentation.
When installing odfdo, a few scripts are installed:
odfdo-diff: show a diff between two .odt document.odfdo-folder: convert standard ODF file to folder and files, and reverse.odfdo-show: dump text from an ODF file to the standard output, and optionally styles and meta informations.odfdo-styles: command line interface tool to manipulate styles of ODF files.odfdo-replace: find a pattern (regex) in an ODF file and replace by some string.odfdo-highlight: highlight the text matching a pattern (regex) in an ODF file.odfdo-headers: print the headers of an ODF file.odfdo-table-shrink: Shrink tables to optimize width and height (experimental).
About styles: the best way to apply style is by merging styles from a template
document into your generated document (See odfdo-styles script).
Styles are a complex matter in ODF, so trying to generate styles programmatically
is not recommended.
Limitations
odfdo is intended to facilitate the generation of ODF documents,
nevertheless a basic knowledge of the ODF format is necessary.
ODF document rendering can vary greatly from software to software. Especially the "styles" of the document allow an adaptation of the rendering for a particular software.
The best (only ?) way to apply style is by merging styles from a template document into your generated document.
Related project
I you work on .ods files (spreadsheet), you may be interested by these scripts that use this library to parse/generate .ods files: https://github.com/jdum/odsgenerator and https://github.com/jdum/odsparsator
Changes from former lpod library
lpod-python was written in 2009-2010 as a Python 2.x library,
see: https://github.com/lpod/lpod-python
odfdo is an adaptation of this former project. odfdo main changes from lpod:
odfdorequires Python version 3.9 to 3.12. For Python 3.6 to 3.8 see previous releases.- API change: more pythonic.
- include recipes.
- use Apache 2.0 license.
1# Copyright 2018-2024 Jérôme Dumonteil 2# Copyright (c) 2009-2010 Ars Aperta, Itaapy, Pierlis, Talend. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15# 16# 17# Authors (odfdo project): jerome.dumonteil@gmail.com 18# The odfdo project is a derivative work of the lpod-python project: 19# https://github.com/lpod/lpod-python 20# Authors: David Versmisse <david.versmisse@itaapy.com> 21# Hervé Cauwelier <herve@itaapy.com> 22# Romain Gauthier <romain@itaapy.com> 23""" 24.. include:: ../README.md 25""" 26 27__all__ = [ 28 "AnimPar", 29 "AnimSeq", 30 "AnimTransFilter", 31 "Annotation", 32 "AnnotationEnd", 33 "BackgroundImage", 34 "Bookmark", 35 "BookmarkEnd", 36 "BookmarkStart", 37 "Cell", 38 "ChangeInfo", 39 "Column", 40 "ConnectorShape", 41 "Container", 42 "Content", 43 "Content", 44 "Document", 45 "DrawFillImage", 46 "DrawGroup", 47 "DrawImage", 48 "DrawPage", 49 "Element", 50 "ElementTyped", 51 "EllipseShape", 52 "FIRST_CHILD", 53 "Frame", 54 "Header", 55 "HeaderRows", 56 "IndexTitle", 57 "IndexTitleTemplate", 58 "LAST_CHILD", 59 "LineBreak", 60 "LineShape", 61 "Link", 62 "List", 63 "ListItem", 64 "Manifest", 65 "Meta", 66 "NEXT_SIBLING", 67 "NamedRange", 68 "Note", 69 "PREV_SIBLING", 70 "PageBreak", 71 "Paragraph", 72 "RectangleShape", 73 "Reference", 74 "ReferenceMark", 75 "ReferenceMarkEnd", 76 "ReferenceMarkStart", 77 "Row", 78 "RowGroup", 79 "Section", 80 "Spacer", 81 "Span", 82 "Style", 83 "Styles", 84 "TOC", 85 "Tab", 86 "TabStopStyle", 87 "Table", 88 "Text", 89 "TextChange", 90 "TextChangeEnd", 91 "TextChangeStart", 92 "TextChangedRegion", 93 "TextDeletion", 94 "TextFormatChange", 95 "TextInsertion", 96 "TocEntryTemplate", 97 "TrackedChanges", 98 "UserDefined", 99 "UserFieldDecl", 100 "UserFieldDecls", 101 "UserFieldGet", 102 "UserFieldInput", 103 "VarChapter", 104 "VarCreationDate", 105 "VarCreationTime", 106 "VarDate", 107 "VarDecl", 108 "VarDecls", 109 "VarDescription", 110 "VarFileName", 111 "VarGet", 112 "VarInitialCreator", 113 "VarKeywords", 114 "VarPageCount", 115 "VarPageNumber", 116 "VarSet", 117 "VarSubject", 118 "VarTime", 119 "VarTitle", 120 "XmlPart", 121 "__version__", 122 "create_table_cell_style", 123 "default_boolean_style", 124 "default_currency_style", 125 "default_date_style", 126 "default_frame_position_style", 127 "default_number_style", 128 "default_percentage_style", 129 "default_time_style", 130 "default_toc_level_style", 131 "hex2rgb", 132 "hexa_color", 133 "make_table_cell_border_string", 134 "rgb2hex", 135] 136 137 138from .bookmark import Bookmark, BookmarkEnd, BookmarkStart 139from .cell import Cell 140from .container import Container 141from .content import Content 142from .document import Document 143from .draw_page import DrawPage 144from .element import FIRST_CHILD, LAST_CHILD, NEXT_SIBLING, PREV_SIBLING, Element, Text 145from .element_typed import ElementTyped 146from .frame import Frame, default_frame_position_style 147from .header import Header 148from .header_rows import HeaderRows 149from .image import DrawFillImage, DrawImage 150from .link import Link 151from .list import List, ListItem 152from .manifest import Manifest 153from .meta import Meta 154from .note import Annotation, AnnotationEnd, Note 155from .paragraph import LineBreak, PageBreak, Paragraph, Spacer, Span, Tab 156from .reference import Reference, ReferenceMark, ReferenceMarkEnd, ReferenceMarkStart 157from .section import Section 158from .shapes import ConnectorShape, DrawGroup, EllipseShape, LineShape, RectangleShape 159from .smil import AnimPar, AnimSeq, AnimTransFilter 160from .style import ( 161 BackgroundImage, 162 Style, 163 create_table_cell_style, 164 default_boolean_style, 165 default_currency_style, 166 default_date_style, 167 default_number_style, 168 default_percentage_style, 169 default_time_style, 170 make_table_cell_border_string, 171) 172from .styles import Styles 173from .table import Column, NamedRange, Row, RowGroup, Table 174from .toc import ( 175 TOC, 176 IndexTitle, 177 IndexTitleTemplate, 178 TabStopStyle, 179 TocEntryTemplate, 180 default_toc_level_style, 181) 182from .tracked_changes import ( 183 ChangeInfo, 184 TextChange, 185 TextChangedRegion, 186 TextChangeEnd, 187 TextChangeStart, 188 TextDeletion, 189 TextFormatChange, 190 TextInsertion, 191 TrackedChanges, 192) 193from .utils import hex2rgb, hexa_color, rgb2hex 194from .variable import ( 195 UserDefined, 196 UserFieldDecl, 197 UserFieldDecls, 198 UserFieldGet, 199 UserFieldInput, 200 VarChapter, 201 VarCreationDate, 202 VarCreationTime, 203 VarDate, 204 VarDecl, 205 VarDecls, 206 VarDescription, 207 VarFileName, 208 VarGet, 209 VarInitialCreator, 210 VarKeywords, 211 VarPageCount, 212 VarPageNumber, 213 VarSet, 214 VarSubject, 215 VarTime, 216 VarTitle, 217) 218from .version import __version__ 219from .xmlpart import XmlPart
32class AnimPar(Element): 33 """A container for SMIL Presentation Animations. 34 35 Arguments: 36 37 presentation_node_type -- default, on-click, with-previous, 38 after-previous, timing-root, main-sequence 39 and interactive-sequence 40 41 smil_begin -- indefinite, 10s, [id].click, [id].begin 42 """ 43 44 _tag = "anim:par" 45 _properties = ( 46 PropDef("presentation_node_type", "presentation:node-type"), 47 PropDef("smil_begin", "smil:begin"), 48 ) 49 50 def __init__( 51 self, 52 presentation_node_type: str | None = None, 53 smil_begin: str | None = None, 54 **kwargs: Any, 55 ) -> None: 56 super().__init__(**kwargs) 57 if self._do_init: 58 if presentation_node_type: 59 self.presentation_node_type = presentation_node_type 60 if smil_begin: 61 self.smil_begin = smil_begin
A container for SMIL Presentation Animations.
Arguments:
presentation_node_type -- default, on-click, with-previous,
after-previous, timing-root, main-sequence
and interactive-sequence
smil_begin -- indefinite, 10s, [id].click, [id].begin
50 def __init__( 51 self, 52 presentation_node_type: str | None = None, 53 smil_begin: str | None = None, 54 **kwargs: Any, 55 ) -> None: 56 super().__init__(**kwargs) 57 if self._do_init: 58 if presentation_node_type: 59 self.presentation_node_type = presentation_node_type 60 if smil_begin: 61 self.smil_begin = smil_begin
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
67class AnimSeq(Element): 68 """TA container for SMIL Presentation Animations. Animations 69 inside this block are executed after the slide has executed its initial 70 transition. 71 72 Arguments: 73 74 presentation_node_type -- default, on-click, with-previous, 75 after-previous, timing-root, main-sequence 76 and interactive-sequence 77 """ 78 79 _tag = "anim:seq" 80 _properties = (PropDef("presentation_node_type", "presentation:node-type"),) 81 82 def __init__( 83 self, 84 presentation_node_type: str | None = None, 85 **kwargs: Any, 86 ) -> None: 87 super().__init__(**kwargs) 88 if self._do_init and presentation_node_type: 89 self.presentation_node_type = presentation_node_type
TA container for SMIL Presentation Animations. Animations inside this block are executed after the slide has executed its initial transition.
Arguments:
presentation_node_type -- default, on-click, with-previous,
after-previous, timing-root, main-sequence
and interactive-sequence
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
95class AnimTransFilter(Element): 96 """ 97 Class to make a beautiful transition between two frames. 98 99 Arguments: 100 smil_dur -- XXX complete me 101 102 smil_type and smil_subtype -- see http://www.w3.org/TR/SMIL20/ 103 smil-transitions.html#TransitionEffects-Appendix 104 to get a list of all types/subtypes 105 106 smil_direction -- forward, reverse 107 108 smil_fadeColor -- forward, reverse 109 110 smil_mode -- in, out 111 """ 112 113 _tag = "anim:transitionFilter" 114 _properties = ( 115 PropDef("smil_dur", "smil:dur"), 116 PropDef("smil_type", "smil:type"), 117 PropDef("smil_subtype", "smil:subtype"), 118 PropDef("smil_direction", "smil:direction"), 119 PropDef("smil_fadeColor", "smil:fadeColor"), 120 PropDef("smil_mode", "smil:mode"), 121 ) 122 123 def __init__( 124 self, 125 smil_dur: str | None = None, 126 smil_type: str | None = None, 127 smil_subtype: str | None = None, 128 smil_direction: str | None = None, 129 smil_fadeColor: str | None = None, 130 smil_mode: str | None = None, 131 **kwargs: Any, 132 ) -> None: 133 super().__init__(**kwargs) 134 if self._do_init: 135 if smil_dur: 136 self.smil_dur = smil_dur 137 if smil_type: 138 self.smil_type = smil_type 139 if smil_subtype: 140 self.smil_subtype = smil_subtype 141 if smil_direction: 142 self.smil_direction = smil_direction 143 if smil_fadeColor: 144 self.smil_fadeColor = smil_fadeColor 145 if smil_mode: 146 self.smil_mode = smil_mode
Class to make a beautiful transition between two frames.
Arguments: smil_dur -- XXX complete me
smil_type and smil_subtype -- see http://www.w3.org/TR/SMIL20/ smil-transitions.html#TransitionEffects-Appendix to get a list of all types/subtypes
smil_direction -- forward, reverse
smil_fadeColor -- forward, reverse
smil_mode -- in, out
123 def __init__( 124 self, 125 smil_dur: str | None = None, 126 smil_type: str | None = None, 127 smil_subtype: str | None = None, 128 smil_direction: str | None = None, 129 smil_fadeColor: str | None = None, 130 smil_mode: str | None = None, 131 **kwargs: Any, 132 ) -> None: 133 super().__init__(**kwargs) 134 if self._do_init: 135 if smil_dur: 136 self.smil_dur = smil_dur 137 if smil_type: 138 self.smil_type = smil_type 139 if smil_subtype: 140 self.smil_subtype = smil_subtype 141 if smil_direction: 142 self.smil_direction = smil_direction 143 if smil_fadeColor: 144 self.smil_fadeColor = smil_fadeColor 145 if smil_mode: 146 self.smil_mode = smil_mode
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
147class Annotation(Element): 148 """Annotation element credited to the given creator with the 149 given text, optionally dated (current date by default). 150 If name not provided and some parent is provided, the name is 151 autogenerated. 152 153 Arguments: 154 155 text -- str or odf_element 156 157 creator -- str 158 159 date -- datetime 160 161 name -- str 162 163 parent -- Element 164 """ 165 166 _tag = "office:annotation" 167 _properties = ( 168 PropDef("name", "office:name"), 169 PropDef("note_id", "text:id"), 170 ) 171 172 def __init__( 173 self, 174 text_or_element: Element | str | None = None, 175 creator: str | None = None, 176 date: datetime | None = None, 177 name: str | None = None, 178 parent: Element | None = None, 179 **kwargs: Any, 180 ) -> None: 181 # fixme : use offset 182 # TODO allow paragraph and text styles 183 super().__init__(**kwargs) 184 185 if self._do_init: 186 self.note_body = text_or_element # type:ignore 187 if creator: 188 self.dc_creator = creator 189 if date is None: 190 date = datetime.now() 191 self.dc_date = date 192 if not name: 193 name = get_unique_office_name(parent) 194 self.name = name 195 196 @property 197 def note_body(self) -> str: 198 return self.text_content 199 200 @note_body.setter 201 def note_body(self, text_or_element: Element | str | None) -> None: 202 if text_or_element is None: 203 self.text_content = "" 204 elif isinstance(text_or_element, str): 205 self.text_content = text_or_element 206 elif isinstance(text_or_element, Element): 207 self.clear() 208 self.append(text_or_element) 209 else: 210 raise TypeError(f'Unexpected type for body: "{type(text_or_element)}"') 211 212 @property 213 def start(self) -> Element: 214 """Return self.""" 215 return self 216 217 @property 218 def end(self) -> Element | None: 219 """Return the corresponding annotation-end tag or None.""" 220 name = self.name 221 parent = self.parent 222 if parent is None: 223 raise ValueError("Can't find end tag: no parent available") 224 body = self.document_body 225 if not body: 226 body = parent 227 return body.get_annotation_end(name=name) 228 229 def get_annotated( 230 self, 231 as_text: bool = False, 232 no_header: bool = True, 233 clean: bool = True, 234 ) -> Element | list | str | None: 235 """Returns the annotated content from an annotation. 236 237 If no content exists (single position annotation or annotation-end not 238 found), returns [] (or "" if text flag is True). 239 If as_text is True: returns the text content. 240 If clean is True: suppress unwanted tags (deletions marks, ...) 241 If no_header is True: existing text:h are changed in text:p 242 By default: returns a list of odf_element, cleaned and without headers. 243 244 Arguments: 245 246 as_text -- boolean 247 248 clean -- boolean 249 250 no_header -- boolean 251 252 Return: list or Element or text or None 253 """ 254 end = self.end 255 if end is None: 256 if as_text: 257 return "" 258 return None 259 body = self.document_body 260 if not body: 261 body = self.root 262 return body.get_between( 263 self, end, as_text=as_text, no_header=no_header, clean=clean 264 ) 265 266 def delete(self, child: Element | None = None, keep_tail: bool = True) -> None: 267 """Delete the given element from the XML tree. If no element is given, 268 "self" is deleted. The XML library may allow to continue to use an 269 element now "orphan" as long as you have a reference to it. 270 271 For Annotation : delete the annotation-end tag if exists. 272 273 Arguments: 274 275 child -- Element or None 276 """ 277 if child is not None: # act like normal delete 278 super().delete(child) 279 return 280 end = self.end 281 if end: 282 end.delete() 283 # act like normal delete 284 super().delete() 285 286 def check_validity(self) -> None: 287 if not self.note_body: 288 raise ValueError("Annotation must have a body") 289 if not self.dc_creator: 290 raise ValueError("Annotation must have a creator") 291 if not self.dc_date: 292 self.dc_date = datetime.now()
Annotation element credited to the given creator with the given text, optionally dated (current date by default). If name not provided and some parent is provided, the name is autogenerated.
Arguments:
text -- str or odf_element
creator -- str
date -- datetime
name -- str
parent -- Element
172 def __init__( 173 self, 174 text_or_element: Element | str | None = None, 175 creator: str | None = None, 176 date: datetime | None = None, 177 name: str | None = None, 178 parent: Element | None = None, 179 **kwargs: Any, 180 ) -> None: 181 # fixme : use offset 182 # TODO allow paragraph and text styles 183 super().__init__(**kwargs) 184 185 if self._do_init: 186 self.note_body = text_or_element # type:ignore 187 if creator: 188 self.dc_creator = creator 189 if date is None: 190 date = datetime.now() 191 self.dc_date = date 192 if not name: 193 name = get_unique_office_name(parent) 194 self.name = name
217 @property 218 def end(self) -> Element | None: 219 """Return the corresponding annotation-end tag or None.""" 220 name = self.name 221 parent = self.parent 222 if parent is None: 223 raise ValueError("Can't find end tag: no parent available") 224 body = self.document_body 225 if not body: 226 body = parent 227 return body.get_annotation_end(name=name)
Return the corresponding annotation-end tag or None.
229 def get_annotated( 230 self, 231 as_text: bool = False, 232 no_header: bool = True, 233 clean: bool = True, 234 ) -> Element | list | str | None: 235 """Returns the annotated content from an annotation. 236 237 If no content exists (single position annotation or annotation-end not 238 found), returns [] (or "" if text flag is True). 239 If as_text is True: returns the text content. 240 If clean is True: suppress unwanted tags (deletions marks, ...) 241 If no_header is True: existing text:h are changed in text:p 242 By default: returns a list of odf_element, cleaned and without headers. 243 244 Arguments: 245 246 as_text -- boolean 247 248 clean -- boolean 249 250 no_header -- boolean 251 252 Return: list or Element or text or None 253 """ 254 end = self.end 255 if end is None: 256 if as_text: 257 return "" 258 return None 259 body = self.document_body 260 if not body: 261 body = self.root 262 return body.get_between( 263 self, end, as_text=as_text, no_header=no_header, clean=clean 264 )
Returns the annotated content from an annotation.
If no content exists (single position annotation or annotation-end not found), returns [] (or "" if text flag is True). If as_text is True: returns the text content. If clean is True: suppress unwanted tags (deletions marks, ...) If no_header is True: existing text:h are changed in text:p By default: returns a list of odf_element, cleaned and without headers.
Arguments:
as_text -- boolean
clean -- boolean
no_header -- boolean
Return: list or Element or text or None
266 def delete(self, child: Element | None = None, keep_tail: bool = True) -> None: 267 """Delete the given element from the XML tree. If no element is given, 268 "self" is deleted. The XML library may allow to continue to use an 269 element now "orphan" as long as you have a reference to it. 270 271 For Annotation : delete the annotation-end tag if exists. 272 273 Arguments: 274 275 child -- Element or None 276 """ 277 if child is not None: # act like normal delete 278 super().delete(child) 279 return 280 end = self.end 281 if end: 282 end.delete() 283 # act like normal delete 284 super().delete()
Delete the given element from the XML tree. If no element is given, "self" is deleted. The XML library may allow to continue to use an element now "orphan" as long as you have a reference to it.
For Annotation : delete the annotation-end tag if exists.
Arguments:
child -- Element or None
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
298class AnnotationEnd(Element): 299 """AnnotationEnd: the "office:annotation-end" element may be used to 300 define the end of a text range of document content that spans element 301 boundaries. In that case, an "office:annotation" element shall precede 302 the "office:annotation-end" element. Both elements shall have the same 303 value for their office:name attribute. The "office:annotation-end" element 304 shall be preceded by an "office:annotation" element that has the same 305 value for its office:name attribute as the "office:annotation-end" 306 element. An "office:annotation-end" element without a preceding 307 "office:annotation" element that has the same name assigned is ignored. 308 """ 309 310 _tag = "office:annotation-end" 311 _properties = (PropDef("name", "office:name"),) 312 313 def __init__( 314 self, 315 annotation: Element | None = None, 316 name: str | None = None, 317 **kwargs: Any, 318 ) -> None: 319 """Initialize an AnnotationEnd element. Either annotation or name must be 320 provided to have proper reference for the annotation-end. 321 322 Arguments: 323 324 annotation -- odf_annotation element 325 326 name -- str 327 """ 328 # fixme : use offset 329 # TODO allow paragraph and text styles 330 super().__init__(**kwargs) 331 if self._do_init: 332 if annotation: 333 name = annotation.name # type: ignore 334 if not name: 335 raise ValueError("Annotation-end must have a name") 336 self.name = name 337 338 @property 339 def start(self) -> Element | None: 340 """Return the corresponding annotation starting tag or None.""" 341 name = self.name 342 parent = self.parent 343 if parent is None: 344 raise ValueError("Can't find start tag: no parent available") 345 body = self.document_body 346 if not body: 347 body = parent 348 return body.get_annotation(name=name) 349 350 @property 351 def end(self) -> Element: 352 """Return self.""" 353 return self
AnnotationEnd: the "office:annotation-end" element may be used to define the end of a text range of document content that spans element boundaries. In that case, an "office:annotation" element shall precede the "office:annotation-end" element. Both elements shall have the same value for their office:name attribute. The "office:annotation-end" element shall be preceded by an "office:annotation" element that has the same value for its office:name attribute as the "office:annotation-end" element. An "office:annotation-end" element without a preceding "office:annotation" element that has the same name assigned is ignored.
313 def __init__( 314 self, 315 annotation: Element | None = None, 316 name: str | None = None, 317 **kwargs: Any, 318 ) -> None: 319 """Initialize an AnnotationEnd element. Either annotation or name must be 320 provided to have proper reference for the annotation-end. 321 322 Arguments: 323 324 annotation -- odf_annotation element 325 326 name -- str 327 """ 328 # fixme : use offset 329 # TODO allow paragraph and text styles 330 super().__init__(**kwargs) 331 if self._do_init: 332 if annotation: 333 name = annotation.name # type: ignore 334 if not name: 335 raise ValueError("Annotation-end must have a name") 336 self.name = name
Initialize an AnnotationEnd element. Either annotation or name must be provided to have proper reference for the annotation-end.
Arguments:
annotation -- odf_annotation element
name -- str
338 @property 339 def start(self) -> Element | None: 340 """Return the corresponding annotation starting tag or None.""" 341 name = self.name 342 parent = self.parent 343 if parent is None: 344 raise ValueError("Can't find start tag: no parent available") 345 body = self.document_body 346 if not body: 347 body = parent 348 return body.get_annotation(name=name)
Return the corresponding annotation starting tag or None.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
999class BackgroundImage(Style, DrawImage): 1000 _tag = "style:background-image" 1001 _properties: tuple[PropDef, ...] = ( 1002 PropDef("name", "style:name"), 1003 PropDef("display_name", "style:display-name"), 1004 PropDef("svg_font_family", "svg:font-family"), 1005 PropDef("font_family_generic", "style:font-family-generic"), 1006 PropDef("font_pitch", "style:font-pitch"), 1007 PropDef("position", "style:position", "background-image"), 1008 PropDef("repeat", "style:repeat", "background-image"), 1009 PropDef("opacity", "draw:opacity", "background-image"), 1010 PropDef("filter", "style:filter-name", "background-image"), 1011 PropDef("text_style", "text:style-name"), 1012 ) 1013 1014 def __init__( 1015 self, 1016 name: str | None = None, 1017 display_name: str | None = None, 1018 position: str | None = None, 1019 repeat: str | None = None, 1020 opacity: str | None = None, 1021 filter: str | None = None, # noqa: A002 1022 # Every other property 1023 **kwargs: Any, 1024 ): 1025 kwargs["family"] = "background-image" 1026 super().__init__(**kwargs) 1027 if self._do_init: 1028 kwargs.pop("tag", None) 1029 kwargs.pop("tag_or_elem", None) 1030 self.family = "background-image" 1031 if name: 1032 self.name = name 1033 if display_name: 1034 self.display_name = display_name 1035 if position: 1036 self.position = position 1037 if repeat: 1038 self.position = repeat 1039 if opacity: 1040 self.position = opacity 1041 if filter: 1042 self.position = filter 1043 # Every other properties 1044 for prop in BackgroundImage._properties: 1045 if prop.name in kwargs: 1046 self.set_style_attribute(prop.attr, kwargs[prop.name])
Style class for all these tags:
'style:style' 'number:date-style', 'number:number-style', 'number:percentage-style', 'number:time-style' 'style:font-face', 'style:master-page', 'style:page-layout', 'style:presentation-page-layout', 'text:list-style', 'text:outline-style', 'style:tab-stops', ...
1014 def __init__( 1015 self, 1016 name: str | None = None, 1017 display_name: str | None = None, 1018 position: str | None = None, 1019 repeat: str | None = None, 1020 opacity: str | None = None, 1021 filter: str | None = None, # noqa: A002 1022 # Every other property 1023 **kwargs: Any, 1024 ): 1025 kwargs["family"] = "background-image" 1026 super().__init__(**kwargs) 1027 if self._do_init: 1028 kwargs.pop("tag", None) 1029 kwargs.pop("tag_or_elem", None) 1030 self.family = "background-image" 1031 if name: 1032 self.name = name 1033 if display_name: 1034 self.display_name = display_name 1035 if position: 1036 self.position = position 1037 if repeat: 1038 self.position = repeat 1039 if opacity: 1040 self.position = opacity 1041 if filter: 1042 self.position = filter 1043 # Every other properties 1044 for prop in BackgroundImage._properties: 1045 if prop.name in kwargs: 1046 self.set_style_attribute(prop.attr, kwargs[prop.name])
Create a style of the given family. The name is not mandatory at this point but will become required when inserting in a document as a common style.
The display name is the name the user sees in an office application.
The parent_style is the name of the style this style will inherit from.
To set properties, pass them as keyword arguments. The area properties apply to is optional and defaults to the family.
Arguments:
family -- 'paragraph', 'text', 'section', 'table', 'table-column',
'table-row', 'table-cell', 'table-page', 'chart',
'drawing-page', 'graphic', 'presentation',
'control', 'ruby', 'list', 'number', 'page-layout'
'font-face', or 'master-page'
name -- str
display_name -- str
parent_style -- str
area -- str
'text' Properties:
italic -- bool
bold -- bool
'paragraph' Properties:
master_page -- str
'master-page' Properties:
page_layout -- str
next_style -- str
'table-cell' Properties:
border, border_top, border_right, border_bottom, border_left -- str,
e.g. "0.002cm solid #000000" or 'none'
padding, padding_top, padding_right, padding_bottom, padding_left -- str,
e.g. "0.002cm" or 'none'
shadow -- str, e.g. "#808080 0.176cm 0.176cm"
'table-row' Properties:
height -- str, e.g. '5cm'
use_optimal_height -- bool
'table-column' Properties:
width -- str, e.g. '5cm'
break_before -- 'page', 'column' or 'auto'
break_after -- 'page', 'column' or 'auto'
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Style
- family
- get_properties
- set_properties
- del_properties
- set_background
- get_level_style
- set_level_style
- get_header_style
- set_header_style
- get_page_header
- set_page_header
- set_font
- page_layout
- next_style
- parent_style
- master_page
- style_type
- leader_style
- leader_text
- style_position
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
31class Bookmark(Element): 32 """ 33 Bookmark class for ODF "text:bookmark" 34 35 Arguments: 36 37 name -- str 38 """ 39 40 _tag = "text:bookmark" 41 _properties = (PropDef("name", "text:name"),) 42 43 def __init__(self, name: str = "", **kwargs: Any) -> None: 44 super().__init__(**kwargs) 45 if self._do_init: 46 self.name = name
Bookmark class for ODF "text:bookmark"
Arguments:
name -- str
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
73class BookmarkEnd(Element): 74 """ 75 BookmarkEnd class for ODF "text:bookmark-end" 76 77 Arguments: 78 79 name -- str 80 """ 81 82 _tag = "text:bookmark-end" 83 _properties = (PropDef("name", "text:name"),) 84 85 def __init__(self, name: str = "", **kwargs: Any) -> None: 86 super().__init__(**kwargs) 87 if self._do_init: 88 self.name = name
BookmarkEnd class for ODF "text:bookmark-end"
Arguments:
name -- str
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
52class BookmarkStart(Element): 53 """ 54 BookmarkStart class for ODF "text:bookmark-start" 55 56 Arguments: 57 58 name -- str 59 """ 60 61 _tag = "text:bookmark-start" 62 _properties = (PropDef("name", "text:name"),) 63 64 def __init__(self, name: str = "", **kwargs: Any) -> None: 65 super().__init__(**kwargs) 66 if self._do_init: 67 self.name = name
BookmarkStart class for ODF "text:bookmark-start"
Arguments:
name -- str
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
44class Cell(ElementTyped): 45 """ "table:table-cell" table cell element.""" 46 47 _tag = "table:table-cell" 48 _caching = True 49 50 def __init__( 51 self, 52 value: Any = None, 53 text: str | None = None, 54 cell_type: str | None = None, 55 currency: str | None = None, 56 formula: str | None = None, 57 repeated: int | None = None, 58 style: str | None = None, 59 **kwargs: Any, 60 ) -> None: 61 """Create a cell element containing the given value. The textual 62 representation is automatically formatted but can be provided. Cell 63 type can be deduced as well, unless the number is a percentage or 64 currency. If cell type is "currency", the currency must be given. 65 The cell can be repeated on the given number of columns. 66 67 Arguments: 68 69 value -- bool, int, float, Decimal, date, datetime, str, 70 timedelta 71 72 text -- str 73 74 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 75 'string' or 'time' 76 77 currency -- three-letter str 78 79 repeated -- int 80 81 style -- str 82 """ 83 super().__init__(**kwargs) 84 self.x = None 85 self.y = None 86 if self._do_init: 87 self.set_value( 88 value, 89 text=text, 90 cell_type=cell_type, 91 currency=currency, 92 formula=formula, 93 ) 94 if repeated and repeated > 1: 95 self.repeated = repeated 96 if style is not None: 97 self.style = style 98 99 def __repr__(self) -> str: 100 return f"<{self.__class__.__name__} x={self.x} y={self.y}>" 101 102 @property 103 def clone(self) -> Cell: 104 clone = Element.clone.fget(self) # type: ignore 105 clone.y = self.y 106 clone.x = self.x 107 if hasattr(self, "_tmap"): 108 if hasattr(self, "_rmap"): 109 clone._rmap = self._rmap[:] 110 clone._tmap = self._tmap[:] 111 clone._cmap = self._cmap[:] 112 return clone 113 114 @property 115 def value( 116 self, 117 ) -> str | bool | int | Float | Decimal | date | datetime | timedelta | None: 118 """Set / get the value of the cell. The type is read from the 119 'office:value-type' attribute of the cell. When setting the value, 120 the type of the value will determine the new value_type of the cell. 121 122 Warning: use this method for boolean, float or string only. 123 """ 124 value_type = self.get_attribute_string("office:value-type") 125 if value_type == "boolean": 126 return self.get_attribute("office:boolean-value") 127 if value_type in {"float", "percentage", "currency"}: 128 value_decimal = Decimal(str(self.get_attribute_string("office:value"))) 129 # Return 3 instead of 3.0 if possible 130 if int(value_decimal) == value_decimal: 131 return int(value_decimal) 132 return value_decimal 133 if value_type == "date": 134 value_str = str(self.get_attribute_string("office:date-value")) 135 if "T" in value_str: 136 return DateTime.decode(value_str) 137 return Date.decode(value_str) 138 if value_type == "time": 139 return Duration.decode(str(self.get_attribute_string("office:time-value"))) 140 if value_type == "string": 141 value = self.get_attribute_string("office:string-value") 142 if value is not None: 143 return value 144 value_list = [] 145 for para in self.get_elements("text:p"): 146 value_list.append(para.text_recursive) 147 return "\n".join(value_list) 148 return None 149 150 @value.setter 151 def value(self, value: str | bytes | bool | int | Float | Decimal | None) -> None: 152 self.clear() 153 if value is None: 154 return 155 if isinstance(value, (str, bytes)): 156 if isinstance(value, bytes): 157 value = bytes_to_str(value) 158 self.set_attribute("office:value-type", "string") 159 self.set_attribute("office:string-value", value) 160 self.text = value 161 return 162 if value is True or value is False: 163 self.set_attribute("office:value-type", "boolean") 164 value_bool = Boolean.encode(value) 165 self.set_attribute("office:boolean-value", value_bool) 166 self.text = value_bool 167 return 168 if isinstance(value, (int, Float, Decimal)): 169 self.set_attribute("office:value-type", "float") 170 value_str = str(value) 171 self.set_attribute("office:value", value_str) 172 self.text = value_str 173 return 174 raise TypeError(f"Unknown value type, try with set_value() : {value!r}") 175 176 @property 177 def float(self) -> Float: 178 """Set / get the value of the cell as a float (or 0.0).""" 179 for tag in ("office:value", "office:string-value", "office:boolean-value"): 180 read_attr = self.get_attribute(tag) 181 if isinstance(read_attr, str): 182 with contextlib.suppress(ValueError, TypeError): 183 return Float(read_attr) 184 return 0.0 185 186 @float.setter 187 def float(self, value: str | Float | int | Decimal) -> None: 188 try: 189 value_float = Float(value) 190 except (ValueError, TypeError): 191 value_float = 0.0 192 value_str = str(value_float) 193 self.clear() 194 self.set_attribute("office:value", value_str) 195 self.set_attribute("office:value-type", "float") 196 self.text = value_str 197 198 @property 199 def string(self) -> str: 200 """Set / get the value of the cell as a string (or '').""" 201 value = self.get_attribute_string("office:string-value") 202 if isinstance(value, str): 203 return value 204 return "" 205 206 @string.setter 207 def string( 208 self, 209 value: str | bytes | int | Float | Decimal | bool | None, # type: ignore 210 ) -> None: 211 self.clear() 212 if value is None: 213 value_str = "" 214 else: 215 value_str = str(value) 216 self.set_attribute("office:value-type", "string") 217 self.set_attribute("office:string-value", value_str) 218 self.text = value_str 219 220 def set_value( 221 self, 222 value: ( 223 str # type: ignore 224 | bytes 225 | Float 226 | int 227 | Decimal 228 | bool 229 | datetime 230 | date 231 | timedelta 232 | None 233 ), 234 text: str | None = None, 235 cell_type: str | None = None, 236 currency: str | None = None, 237 formula: str | None = None, 238 ) -> None: 239 """Set the cell state from the Python value type. 240 241 Text is how the cell is displayed. Cell type is guessed, 242 unless provided. 243 244 For monetary values, provide the name of the currency. 245 246 Arguments: 247 248 value -- Python type 249 250 text -- str 251 252 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 253 'currency' or 'percentage' 254 255 currency -- str 256 """ 257 self.clear() 258 text = self.set_value_and_type( 259 value=value, 260 text=text, 261 value_type=cell_type, 262 currency=currency, 263 ) 264 if text is not None: 265 self.text_content = text 266 if formula is not None: 267 self.formula = formula 268 269 @property 270 def type(self) -> str | None: 271 """Get / set the type of the cell: boolean, float, date, string 272 or time. 273 274 Return: str | None 275 """ 276 return self.get_attribute_string("office:value-type") 277 278 @type.setter 279 def type(self, cell_type: str) -> None: 280 self.set_attribute("office:value-type", cell_type) 281 282 @property 283 def currency(self) -> str | None: 284 """Get / set the currency used for monetary values. 285 286 Return: str | None 287 """ 288 return self.get_attribute_string("office:currency") 289 290 @currency.setter 291 def currency(self, currency: str) -> None: 292 self.set_attribute("office:currency", currency) 293 294 def _set_repeated(self, repeated: int | None) -> None: 295 """Internal only. Set the numnber of times the cell is repeated, or 296 None to delete. Without changing cache. 297 """ 298 if repeated is None or repeated < 2: 299 with contextlib.suppress(KeyError): 300 self.del_attribute("table:number-columns-repeated") 301 return 302 self.set_attribute("table:number-columns-repeated", str(repeated)) 303 304 @property 305 def repeated(self) -> int | None: 306 """Get / set the number of times the cell is repeated. 307 308 Always None when using the table API. 309 310 Return: int or None 311 """ 312 repeated = self.get_attribute("table:number-columns-repeated") 313 if repeated is None: 314 return None 315 return int(repeated) 316 317 @repeated.setter 318 def repeated(self, repeated: int | None) -> None: 319 self._set_repeated(repeated) 320 # update cache 321 child: Element = self 322 while True: 323 # look for Row, parent may be group of rows 324 upper = child.parent 325 if not upper: 326 # lonely cell 327 return 328 # parent may be group of rows, not table 329 if isinstance(upper, Element) and upper._tag == "table:table-row": 330 break 331 child = upper 332 # fixme : need to optimize this 333 if isinstance(upper, Element) and upper._tag == "table:table-row": 334 upper._compute_row_cache() 335 336 @property 337 def style(self) -> str | None: 338 """Get / set the style of the cell itself. 339 340 Return: str | None 341 """ 342 return self.get_attribute_string("table:style-name") 343 344 @style.setter 345 def style(self, style: str | Element) -> None: 346 self.set_style_attribute("table:style-name", style) 347 348 @property 349 def formula(self) -> str | None: 350 """Get / set the formula of the cell, or None if undefined. 351 352 The formula is not interpreted in any way. 353 354 Return: str | None 355 """ 356 return self.get_attribute_string("table:formula") 357 358 @formula.setter 359 def formula(self, formula: str | None) -> None: 360 self.set_attribute("table:formula", formula) 361 362 def is_empty(self, aggressive: bool = False) -> bool: 363 """Return whether the cell has no value or the value evaluates 364 to False (empty string), and no style. 365 366 If aggressive is True, empty cells with style are considered empty. 367 368 Arguments: 369 370 aggressive -- bool 371 372 Return: bool 373 """ 374 if self.value is not None or self.children or self.is_spanned(): 375 return False 376 if not aggressive and self.style is not None: 377 return False 378 return True 379 380 def is_spanned(self) -> bool: 381 """Return whether the cell is spanned over several cells. 382 383 Returns: True | False 384 """ 385 if self.tag == "table:covered-table-cell": 386 return True 387 if self.get_attribute("table:number-columns-spanned") is not None: 388 return True 389 if self.get_attribute("table:number-rows-spanned") is not None: 390 return True 391 return False 392 393 _is_spanned = is_spanned # compatibility
"table:table-cell" table cell element.
50 def __init__( 51 self, 52 value: Any = None, 53 text: str | None = None, 54 cell_type: str | None = None, 55 currency: str | None = None, 56 formula: str | None = None, 57 repeated: int | None = None, 58 style: str | None = None, 59 **kwargs: Any, 60 ) -> None: 61 """Create a cell element containing the given value. The textual 62 representation is automatically formatted but can be provided. Cell 63 type can be deduced as well, unless the number is a percentage or 64 currency. If cell type is "currency", the currency must be given. 65 The cell can be repeated on the given number of columns. 66 67 Arguments: 68 69 value -- bool, int, float, Decimal, date, datetime, str, 70 timedelta 71 72 text -- str 73 74 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 75 'string' or 'time' 76 77 currency -- three-letter str 78 79 repeated -- int 80 81 style -- str 82 """ 83 super().__init__(**kwargs) 84 self.x = None 85 self.y = None 86 if self._do_init: 87 self.set_value( 88 value, 89 text=text, 90 cell_type=cell_type, 91 currency=currency, 92 formula=formula, 93 ) 94 if repeated and repeated > 1: 95 self.repeated = repeated 96 if style is not None: 97 self.style = style
Create a cell element containing the given value. The textual representation is automatically formatted but can be provided. Cell type can be deduced as well, unless the number is a percentage or currency. If cell type is "currency", the currency must be given. The cell can be repeated on the given number of columns.
Arguments:
value -- bool, int, float, Decimal, date, datetime, str,
timedelta
text -- str
cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
'string' or 'time'
currency -- three-letter str
repeated -- int
style -- str
102 @property 103 def clone(self) -> Cell: 104 clone = Element.clone.fget(self) # type: ignore 105 clone.y = self.y 106 clone.x = self.x 107 if hasattr(self, "_tmap"): 108 if hasattr(self, "_rmap"): 109 clone._rmap = self._rmap[:] 110 clone._tmap = self._tmap[:] 111 clone._cmap = self._cmap[:] 112 return clone
114 @property 115 def value( 116 self, 117 ) -> str | bool | int | Float | Decimal | date | datetime | timedelta | None: 118 """Set / get the value of the cell. The type is read from the 119 'office:value-type' attribute of the cell. When setting the value, 120 the type of the value will determine the new value_type of the cell. 121 122 Warning: use this method for boolean, float or string only. 123 """ 124 value_type = self.get_attribute_string("office:value-type") 125 if value_type == "boolean": 126 return self.get_attribute("office:boolean-value") 127 if value_type in {"float", "percentage", "currency"}: 128 value_decimal = Decimal(str(self.get_attribute_string("office:value"))) 129 # Return 3 instead of 3.0 if possible 130 if int(value_decimal) == value_decimal: 131 return int(value_decimal) 132 return value_decimal 133 if value_type == "date": 134 value_str = str(self.get_attribute_string("office:date-value")) 135 if "T" in value_str: 136 return DateTime.decode(value_str) 137 return Date.decode(value_str) 138 if value_type == "time": 139 return Duration.decode(str(self.get_attribute_string("office:time-value"))) 140 if value_type == "string": 141 value = self.get_attribute_string("office:string-value") 142 if value is not None: 143 return value 144 value_list = [] 145 for para in self.get_elements("text:p"): 146 value_list.append(para.text_recursive) 147 return "\n".join(value_list) 148 return None
Set / get the value of the cell. The type is read from the 'office:value-type' attribute of the cell. When setting the value, the type of the value will determine the new value_type of the cell.
Warning: use this method for boolean, float or string only.
176 @property 177 def float(self) -> Float: 178 """Set / get the value of the cell as a float (or 0.0).""" 179 for tag in ("office:value", "office:string-value", "office:boolean-value"): 180 read_attr = self.get_attribute(tag) 181 if isinstance(read_attr, str): 182 with contextlib.suppress(ValueError, TypeError): 183 return Float(read_attr) 184 return 0.0
Set / get the value of the cell as a float (or 0.0).
198 @property 199 def string(self) -> str: 200 """Set / get the value of the cell as a string (or '').""" 201 value = self.get_attribute_string("office:string-value") 202 if isinstance(value, str): 203 return value 204 return ""
Set / get the value of the cell as a string (or '').
220 def set_value( 221 self, 222 value: ( 223 str # type: ignore 224 | bytes 225 | Float 226 | int 227 | Decimal 228 | bool 229 | datetime 230 | date 231 | timedelta 232 | None 233 ), 234 text: str | None = None, 235 cell_type: str | None = None, 236 currency: str | None = None, 237 formula: str | None = None, 238 ) -> None: 239 """Set the cell state from the Python value type. 240 241 Text is how the cell is displayed. Cell type is guessed, 242 unless provided. 243 244 For monetary values, provide the name of the currency. 245 246 Arguments: 247 248 value -- Python type 249 250 text -- str 251 252 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 253 'currency' or 'percentage' 254 255 currency -- str 256 """ 257 self.clear() 258 text = self.set_value_and_type( 259 value=value, 260 text=text, 261 value_type=cell_type, 262 currency=currency, 263 ) 264 if text is not None: 265 self.text_content = text 266 if formula is not None: 267 self.formula = formula
Set the cell state from the Python value type.
Text is how the cell is displayed. Cell type is guessed, unless provided.
For monetary values, provide the name of the currency.
Arguments:
value -- Python type
text -- str
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency' or 'percentage'
currency -- str
269 @property 270 def type(self) -> str | None: 271 """Get / set the type of the cell: boolean, float, date, string 272 or time. 273 274 Return: str | None 275 """ 276 return self.get_attribute_string("office:value-type")
Get / set the type of the cell: boolean, float, date, string or time.
Return: str | None
282 @property 283 def currency(self) -> str | None: 284 """Get / set the currency used for monetary values. 285 286 Return: str | None 287 """ 288 return self.get_attribute_string("office:currency")
Get / set the currency used for monetary values.
Return: str | None
304 @property 305 def repeated(self) -> int | None: 306 """Get / set the number of times the cell is repeated. 307 308 Always None when using the table API. 309 310 Return: int or None 311 """ 312 repeated = self.get_attribute("table:number-columns-repeated") 313 if repeated is None: 314 return None 315 return int(repeated)
Get / set the number of times the cell is repeated.
Always None when using the table API.
Return: int or None
336 @property 337 def style(self) -> str | None: 338 """Get / set the style of the cell itself. 339 340 Return: str | None 341 """ 342 return self.get_attribute_string("table:style-name")
Get / set the style of the cell itself.
Return: str | None
348 @property 349 def formula(self) -> str | None: 350 """Get / set the formula of the cell, or None if undefined. 351 352 The formula is not interpreted in any way. 353 354 Return: str | None 355 """ 356 return self.get_attribute_string("table:formula")
Get / set the formula of the cell, or None if undefined.
The formula is not interpreted in any way.
Return: str | None
362 def is_empty(self, aggressive: bool = False) -> bool: 363 """Return whether the cell has no value or the value evaluates 364 to False (empty string), and no style. 365 366 If aggressive is True, empty cells with style are considered empty. 367 368 Arguments: 369 370 aggressive -- bool 371 372 Return: bool 373 """ 374 if self.value is not None or self.children or self.is_spanned(): 375 return False 376 if not aggressive and self.style is not None: 377 return False 378 return True
Return whether the cell has no value or the value evaluates to False (empty string), and no style.
If aggressive is True, empty cells with style are considered empty.
Arguments:
aggressive -- bool
Return: bool
380 def is_spanned(self) -> bool: 381 """Return whether the cell is spanned over several cells. 382 383 Returns: True | False 384 """ 385 if self.tag == "table:covered-table-cell": 386 return True 387 if self.get_attribute("table:number-columns-spanned") is not None: 388 return True 389 if self.get_attribute("table:number-rows-spanned") is not None: 390 return True 391 return False
Return whether the cell is spanned over several cells.
Returns: True | False
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
37class ChangeInfo(Element): 38 """The "office:change-info" element represents who made a change and when. 39 It may also contain a comment (one or more Paragrah "text:p" elements) 40 on the change. 41 42 The comments available in the ChangeInfo are available through: 43 - get_paragraphs and get_paragraph methods for actual Paragraph. 44 - get_comments for a plain text version 45 46 Arguments: 47 48 creator -- str (or None) 49 50 date -- datetime (or None) 51 """ 52 53 _tag = "office:change-info" 54 55 def __init__( 56 self, 57 creator: str | None = None, 58 date: datetime | None = None, 59 **kwargs: Any, 60 ) -> None: 61 super().__init__(**kwargs) 62 if self._do_init: 63 self.set_dc_creator(creator) 64 self.set_dc_date(date) 65 66 def set_dc_creator(self, creator: str | None = None) -> None: 67 """Set the creator of the change. Default for creator is 'Unknown'. 68 69 Arguments: 70 71 creator -- str (or None) 72 """ 73 element = self.get_element("dc:creator") 74 if element is None: 75 element = Element.from_tag("dc:creator") 76 self.insert(element, xmlposition=FIRST_CHILD) 77 if not creator: 78 creator = "Unknown" 79 element.text = creator 80 81 def set_dc_date(self, date: datetime | None = None) -> None: 82 """Set the date of the change. If date is None, use current time. 83 84 Arguments: 85 86 date -- datetime (or None) 87 """ 88 if date is None: 89 date = datetime.now() 90 dcdate = DateTime.encode(date) 91 element = self.get_element("dc:date") 92 if element is None: 93 element = Element.from_tag("dc:date") 94 self.insert(element, xmlposition=LAST_CHILD) 95 element.text = dcdate 96 97 def get_comments(self, joined: bool = True) -> str | list[str]: 98 """Get text content of the comments. If joined is True (default), the 99 text of different paragraphs is concatenated, else a list of strings, 100 one per paragraph, is returned. 101 102 Arguments: 103 104 joined -- boolean (default is True) 105 106 Return: str or list of str. 107 """ 108 content = self.get_paragraphs() 109 if content is None: 110 content = [] 111 text = [para.get_formatted_text(simple=True) for para in content] # type: ignore 112 if joined: 113 return "\n".join(text) 114 return text 115 116 def set_comments(self, text: str = "", replace: bool = True) -> None: 117 """Set the text content of the comments. If replace is True (default), 118 the new text replace old comments, else it is added at the end. 119 120 Arguments: 121 122 text -- str 123 124 replace -- boolean 125 """ 126 if replace: 127 for para in self.get_paragraphs(): 128 self.delete(para) 129 para = Paragraph() 130 para.append_plain_text(text) 131 self.insert(para, xmlposition=LAST_CHILD)
The "office:change-info" element represents who made a change and when. It may also contain a comment (one or more Paragrah "text:p" elements) on the change.
The comments available in the ChangeInfo are available through:
- get_paragraphs and get_paragraph methods for actual Paragraph.
- get_comments for a plain text version
Arguments:
creator -- str (or None)
date -- datetime (or None)
66 def set_dc_creator(self, creator: str | None = None) -> None: 67 """Set the creator of the change. Default for creator is 'Unknown'. 68 69 Arguments: 70 71 creator -- str (or None) 72 """ 73 element = self.get_element("dc:creator") 74 if element is None: 75 element = Element.from_tag("dc:creator") 76 self.insert(element, xmlposition=FIRST_CHILD) 77 if not creator: 78 creator = "Unknown" 79 element.text = creator
Set the creator of the change. Default for creator is 'Unknown'.
Arguments:
creator -- str (or None)
81 def set_dc_date(self, date: datetime | None = None) -> None: 82 """Set the date of the change. If date is None, use current time. 83 84 Arguments: 85 86 date -- datetime (or None) 87 """ 88 if date is None: 89 date = datetime.now() 90 dcdate = DateTime.encode(date) 91 element = self.get_element("dc:date") 92 if element is None: 93 element = Element.from_tag("dc:date") 94 self.insert(element, xmlposition=LAST_CHILD) 95 element.text = dcdate
Set the date of the change. If date is None, use current time.
Arguments:
date -- datetime (or None)
97 def get_comments(self, joined: bool = True) -> str | list[str]: 98 """Get text content of the comments. If joined is True (default), the 99 text of different paragraphs is concatenated, else a list of strings, 100 one per paragraph, is returned. 101 102 Arguments: 103 104 joined -- boolean (default is True) 105 106 Return: str or list of str. 107 """ 108 content = self.get_paragraphs() 109 if content is None: 110 content = [] 111 text = [para.get_formatted_text(simple=True) for para in content] # type: ignore 112 if joined: 113 return "\n".join(text) 114 return text
Get text content of the comments. If joined is True (default), the text of different paragraphs is concatenated, else a list of strings, one per paragraph, is returned.
Arguments:
joined -- boolean (default is True)
Return: str or list of str.
116 def set_comments(self, text: str = "", replace: bool = True) -> None: 117 """Set the text content of the comments. If replace is True (default), 118 the new text replace old comments, else it is added at the end. 119 120 Arguments: 121 122 text -- str 123 124 replace -- boolean 125 """ 126 if replace: 127 for para in self.get_paragraphs(): 128 self.delete(para) 129 para = Paragraph() 130 para.append_plain_text(text) 131 self.insert(para, xmlposition=LAST_CHILD)
Set the text content of the comments. If replace is True (default), the new text replace old comments, else it is added at the end.
Arguments:
text -- str
replace -- boolean
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
164class Column(Element): 165 """ODF table column "table:table-column" """ 166 167 _tag = "table:table-column" 168 _caching = True 169 170 def __init__( 171 self, 172 default_cell_style: str | None = None, 173 repeated: int | None = None, 174 style: str | None = None, 175 **kwargs: Any, 176 ) -> None: 177 """Create a column group element of the optionally given style. Cell 178 style can be set for the whole column. If the properties apply to 179 several columns, give the number of repeated columns. 180 181 Columns don't contain cells, just style information. 182 183 You don't generally have to create columns by hand, use the Table API. 184 185 Arguments: 186 187 default_cell_style -- str 188 189 repeated -- int 190 191 style -- str 192 """ 193 super().__init__(**kwargs) 194 self.x = None 195 if self._do_init: 196 if default_cell_style: 197 self.set_default_cell_style(default_cell_style) 198 if repeated and repeated > 1: 199 self.repeated = repeated 200 if style: 201 self.style = style 202 203 def __repr__(self) -> str: 204 return f"<{self.__class__.__name__} x={self.x}>" 205 206 @property 207 def clone(self) -> Column: 208 clone = Element.clone.fget(self) # type: ignore 209 clone.x = self.x 210 if hasattr(self, "_tmap"): 211 if hasattr(self, "_rmap"): 212 clone._rmap = self._rmap[:] 213 clone._tmap = self._tmap[:] 214 clone._cmap = self._cmap[:] 215 return clone 216 217 def get_default_cell_style(self) -> str | None: 218 return self.get_attribute_string("table:default-cell-style-name") 219 220 def set_default_cell_style(self, style: Element | str) -> None: 221 self.set_style_attribute("table:default-cell-style-name", style) 222 223 def _set_repeated(self, repeated: int | None) -> None: 224 """Internal only. Set the number of times the column is repeated, or 225 None to delete it. Without changing cache. 226 227 Arguments: 228 229 repeated -- int or None 230 """ 231 if repeated is None or repeated < 2: 232 with contextlib.suppress(KeyError): 233 self.del_attribute("table:number-columns-repeated") 234 return 235 self.set_attribute("table:number-columns-repeated", str(repeated)) 236 237 @property 238 def repeated(self) -> int | None: 239 """Get /set the number of times the column is repeated. 240 241 Always None when using the table API. 242 243 Return: int or None 244 """ 245 repeated = self.get_attribute("table:number-columns-repeated") 246 if repeated is None: 247 return None 248 return int(repeated) 249 250 @repeated.setter 251 def repeated(self, repeated: int | None) -> None: 252 self._set_repeated(repeated) 253 # update cache 254 current: Element = self 255 while True: 256 # look for Table, parent may be group of rows 257 upper = current.parent 258 if not upper: 259 # lonely column 260 return 261 # parent may be group of rows, not table 262 if isinstance(upper, Table): 263 break 264 current = upper 265 # fixme : need to optimize this 266 if isinstance(upper, Table): 267 upper._compute_table_cache() 268 if hasattr(self, "_cmap"): 269 del self._cmap[:] 270 self._cmap.extend(upper._cmap) 271 else: 272 self._cmap = upper._cmap 273 274 @property 275 def style(self) -> str | None: 276 """Get /set the style of the column itself. 277 278 Return: str 279 """ 280 return self.get_attribute_string("table:style-name") 281 282 @style.setter 283 def style(self, style: str | Element) -> None: 284 self.set_style_attribute("table:style-name", style)
ODF table column "table:table-column"
170 def __init__( 171 self, 172 default_cell_style: str | None = None, 173 repeated: int | None = None, 174 style: str | None = None, 175 **kwargs: Any, 176 ) -> None: 177 """Create a column group element of the optionally given style. Cell 178 style can be set for the whole column. If the properties apply to 179 several columns, give the number of repeated columns. 180 181 Columns don't contain cells, just style information. 182 183 You don't generally have to create columns by hand, use the Table API. 184 185 Arguments: 186 187 default_cell_style -- str 188 189 repeated -- int 190 191 style -- str 192 """ 193 super().__init__(**kwargs) 194 self.x = None 195 if self._do_init: 196 if default_cell_style: 197 self.set_default_cell_style(default_cell_style) 198 if repeated and repeated > 1: 199 self.repeated = repeated 200 if style: 201 self.style = style
Create a column group element of the optionally given style. Cell style can be set for the whole column. If the properties apply to several columns, give the number of repeated columns.
Columns don't contain cells, just style information.
You don't generally have to create columns by hand, use the Table API.
Arguments:
default_cell_style -- str
repeated -- int
style -- str
237 @property 238 def repeated(self) -> int | None: 239 """Get /set the number of times the column is repeated. 240 241 Always None when using the table API. 242 243 Return: int or None 244 """ 245 repeated = self.get_attribute("table:number-columns-repeated") 246 if repeated is None: 247 return None 248 return int(repeated)
Get /set the number of times the column is repeated.
Always None when using the table API.
Return: int or None
274 @property 275 def style(self) -> str | None: 276 """Get /set the style of the column itself. 277 278 Return: str 279 """ 280 return self.get_attribute_string("table:style-name")
Get /set the style of the column itself.
Return: str
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
242class ConnectorShape(ShapeBase): 243 """Create a Connector shape. 244 245 Arguments: 246 247 style -- str 248 249 text_style -- str 250 251 draw_id -- str 252 253 layer -- str 254 255 connected_shapes -- (shape, shape) 256 257 glue_points -- (point, point) 258 259 p1 -- (str, str) 260 261 p2 -- (str, str) 262 """ 263 264 _tag = "draw:connector" 265 _properties: tuple[PropDef, ...] = ( 266 PropDef("start_shape", "draw:start-shape"), 267 PropDef("end_shape", "draw:end-shape"), 268 PropDef("start_glue_point", "draw:start-glue-point"), 269 PropDef("end_glue_point", "draw:end-glue-point"), 270 PropDef("x1", "svg:x1"), 271 PropDef("y1", "svg:y1"), 272 PropDef("x2", "svg:x2"), 273 PropDef("y2", "svg:y2"), 274 ) 275 276 def __init__( 277 self, 278 style: str | None = None, 279 text_style: str | None = None, 280 draw_id: str | None = None, 281 layer: str | None = None, 282 connected_shapes: tuple | None = None, 283 glue_points: tuple | None = None, 284 p1: tuple | None = None, 285 p2: tuple | None = None, 286 **kwargs: Any, 287 ) -> None: 288 kwargs.update( 289 { 290 "style": style, 291 "text_style": text_style, 292 "draw_id": draw_id, 293 "layer": layer, 294 } 295 ) 296 super().__init__(**kwargs) 297 if self._do_init: 298 if connected_shapes: 299 self.start_shape = connected_shapes[0].draw_id 300 self.end_shape = connected_shapes[1].draw_id 301 if glue_points: 302 self.start_glue_point = glue_points[0] 303 self.end_glue_point = glue_points[1] 304 if p1: 305 self.x1 = p1[0] 306 self.y1 = p1[1] 307 if p2: 308 self.x2 = p2[0] 309 self.y2 = p2[1]
Create a Connector shape.
Arguments:
style -- str
text_style -- str
draw_id -- str
layer -- str
connected_shapes -- (shape, shape)
glue_points -- (point, point)
p1 -- (str, str)
p2 -- (str, str)
276 def __init__( 277 self, 278 style: str | None = None, 279 text_style: str | None = None, 280 draw_id: str | None = None, 281 layer: str | None = None, 282 connected_shapes: tuple | None = None, 283 glue_points: tuple | None = None, 284 p1: tuple | None = None, 285 p2: tuple | None = None, 286 **kwargs: Any, 287 ) -> None: 288 kwargs.update( 289 { 290 "style": style, 291 "text_style": text_style, 292 "draw_id": draw_id, 293 "layer": layer, 294 } 295 ) 296 super().__init__(**kwargs) 297 if self._do_init: 298 if connected_shapes: 299 self.start_shape = connected_shapes[0].draw_id 300 self.end_shape = connected_shapes[1].draw_id 301 if glue_points: 302 self.start_glue_point = glue_points[0] 303 self.end_glue_point = glue_points[1] 304 if p1: 305 self.x1 = p1[0] 306 self.y1 = p1[1] 307 if p2: 308 self.x2 = p2[0] 309 self.y2 = p2[1]
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- odfdo.shapes.ShapeBase
- get_formatted_text
- draw_id
- layer
- width
- height
- pos_x
- pos_y
- presentation_class
- style
- text_style
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
- odfdo.frame.SizeMix
- size
- odfdo.frame.PosMix
- position
55class Container: 56 """Representation of the ODF file.""" 57 58 def __init__(self, path: Path | str | io.BytesIO | None = None) -> None: 59 self.__parts: dict[str, bytes | None] = {} 60 self.__parts_ts: dict[str, int] = {} 61 self.__path_like: Path | str | io.BytesIO | None = None 62 self.__packaging: str = "zip" 63 self.path: Path | None = None # or Path 64 if path: 65 self.open(path) 66 67 def __repr__(self) -> str: 68 return f"<{self.__class__.__name__} type={self.mimetype} path={self.path}>" 69 70 def open(self, path_or_file: Path | str | io.BytesIO) -> None: 71 """Load the content of an ODF file.""" 72 self.__path_like = path_or_file 73 if isinstance(path_or_file, (str, Path)): 74 self.path = Path(path_or_file).expanduser() 75 if not self.path.exists(): 76 raise FileNotFoundError(str(self.path)) 77 self.__path_like = self.path 78 if (self.path or isinstance(self.__path_like, io.BytesIO)) and is_zipfile( 79 self.__path_like # type: ignore 80 ): 81 self.__packaging = "zip" 82 return self._read_zip() 83 if self.path: 84 is_folder = False 85 with contextlib.suppress(OSError): 86 is_folder = self.path.is_dir() 87 if is_folder: 88 self.__packaging = "folder" 89 return self._read_folder() 90 raise TypeError(f"Document format not managed by odfdo: {type(path_or_file)}.") 91 92 def _read_zip(self) -> None: 93 if isinstance(self.__path_like, io.BytesIO): 94 self.__path_like.seek(0) 95 with ZipFile(self.__path_like) as zf: # type: ignore 96 mimetype = bytes_to_str(zf.read("mimetype")) 97 if mimetype not in ODF_MIMETYPES: 98 raise ValueError(f"Document of unknown type {mimetype}") 99 self.__parts["mimetype"] = str_to_bytes(mimetype) 100 if self.path is None: 101 if isinstance(self.__path_like, io.BytesIO): 102 self.__path_like.seek(0) 103 # read the full file at once and forget file 104 with ZipFile(self.__path_like) as zf: # type: ignore 105 for name in zf.namelist(): 106 upath = normalize_path(name) 107 self.__parts[upath] = zf.read(name) 108 self.__path_like = None 109 110 def _read_folder(self) -> None: 111 try: 112 mimetype, timestamp = self._get_folder_part("mimetype") 113 except OSError: 114 printwarn("Corrupted or not an OpenDocument folder (missing mimetype)") 115 mimetype = b"" 116 timestamp = int(time.time()) 117 if bytes_to_str(mimetype) not in ODF_MIMETYPES: 118 message = f"Document of unknown type {mimetype!r}, try with ODF Text." 119 printwarn(message) 120 self.__parts["mimetype"] = str_to_bytes(ODF_EXTENSIONS["odt"]) 121 self.__parts_ts["mimetype"] = timestamp 122 123 def _parse_folder(self, folder: str) -> list[str]: 124 parts = [] 125 if self.path is None: 126 raise ValueError("Document path is not defined") 127 root = self.path / folder 128 for path in root.iterdir(): 129 if path.name.startswith("."): # no hidden files 130 continue 131 relative_path = path.relative_to(self.path) 132 if path.is_file(): 133 parts.append(relative_path.as_posix()) 134 if path.is_dir(): 135 sub_parts = self._parse_folder(str(relative_path)) 136 if sub_parts: 137 parts.extend(sub_parts) 138 else: 139 # store leaf directories 140 parts.append(relative_path.as_posix() + "/") 141 return parts 142 143 def _get_folder_parts(self) -> list[str]: 144 """Get the list of members in the ODF folder.""" 145 return self._parse_folder("") 146 147 def _get_folder_part(self, name: str) -> tuple[bytes, int]: 148 """Get bytes of a part from the ODF folder, with timestamp.""" 149 if self.path is None: 150 raise ValueError("Document path is not defined") 151 path = self.path / name 152 timestamp = int(path.stat().st_mtime) 153 if path.is_dir(): 154 return (b"", timestamp) 155 return (path.read_bytes(), timestamp) 156 157 def _get_folder_part_timestamp(self, name: str) -> int: 158 if self.path is None: 159 raise ValueError("Document path is not defined") 160 path = self.path / name 161 try: 162 timestamp = int(path.stat().st_mtime) 163 except OSError: 164 timestamp = -1 165 return timestamp 166 167 def _get_zip_part(self, name: str) -> bytes | None: 168 """Get bytes of a part from the Zip ODF file. No cache.""" 169 if self.path is None: 170 raise ValueError("Document path is not defined") 171 try: 172 with ZipFile(self.path) as zf: 173 upath = normalize_path(name) 174 self.__parts[upath] = zf.read(name) 175 return self.__parts[upath] 176 except BadZipfile: 177 return None 178 179 def _get_all_zip_part(self) -> None: 180 """Read all parts. No cache.""" 181 if self.path is None: 182 raise ValueError("Document path is not defined") 183 try: 184 with ZipFile(self.path) as zf: 185 for name in zf.namelist(): 186 upath = normalize_path(name) 187 self.__parts[upath] = zf.read(name) 188 except BadZipfile: 189 pass 190 191 def _save_zip(self, target: str | Path | io.BytesIO) -> None: 192 """Save a Zip ODF from the available parts.""" 193 parts = self.__parts 194 with ZipFile(target, "w", compression=ZIP_DEFLATED) as filezip: 195 # Parts to save, except manifest at the end 196 part_names = list(parts.keys()) 197 try: 198 part_names.remove(ODF_MANIFEST) 199 except ValueError: 200 printwarn(f"Missing '{ODF_MANIFEST}'") 201 # "Pretty-save" parts in some order 202 # mimetype requires to be first and uncompressed 203 mimetype = parts.get("mimetype") 204 if mimetype is None: 205 raise ValueError("Mimetype is not defined") 206 try: 207 filezip.writestr("mimetype", mimetype, ZIP_STORED) 208 part_names.remove("mimetype") 209 except (ValueError, KeyError): 210 printwarn("Missing 'mimetype'") 211 # XML parts 212 for path in ODF_CONTENT, ODF_META, ODF_SETTINGS, ODF_STYLES: 213 if path not in parts: 214 printwarn(f"Missing '{path}'") 215 continue 216 part = parts[path] 217 if part is None: 218 continue 219 filezip.writestr(path, part) 220 part_names.remove(path) 221 # Everything else 222 for path in part_names: 223 data = parts[path] 224 if data is None: 225 # Deleted 226 continue 227 filezip.writestr(path, data) 228 # Manifest 229 with contextlib.suppress(KeyError): 230 part = parts[ODF_MANIFEST] 231 if part is not None: 232 filezip.writestr(ODF_MANIFEST, part) 233 234 def _save_folder(self, folder: Path | str) -> None: 235 """Save a folder ODF from the available parts.""" 236 237 def dump(part_path: str, content: bytes) -> None: 238 if part_path.endswith("/"): # folder 239 is_folder = True 240 pure_path = PurePath(folder, part_path[:-1]) 241 else: 242 is_folder = False 243 pure_path = PurePath(folder, part_path) 244 path = Path(pure_path) 245 if is_folder: 246 path.mkdir(parents=True, exist_ok=True) 247 else: 248 path.parent.mkdir(parents=True, exist_ok=True) 249 path.write_bytes(content) 250 path.chmod(0o666) 251 252 for part_path, data in self.__parts.items(): 253 if data is None: 254 # Deleted 255 continue 256 dump(part_path, data) 257 258 # Public API 259 260 def get_parts(self) -> list[str]: 261 """Get the list of members.""" 262 if not self.path: 263 # maybe a file like zip archive 264 return list(self.__parts.keys()) 265 if self.__packaging == "zip": 266 parts = [] 267 with ZipFile(self.path) as zf: 268 for name in zf.namelist(): 269 upath = normalize_path(name) 270 parts.append(upath) 271 return parts 272 elif self.__packaging == "folder": 273 return self._get_folder_parts() 274 else: 275 raise ValueError("Unable to provide parts of the document") 276 277 def get_part(self, path: str) -> str | bytes | None: 278 """Get the bytes of a part of the ODF.""" 279 path = str(path) 280 if path in self.__parts: 281 part = self.__parts[path] 282 if part is None: 283 raise ValueError(f'Part "{path}" is deleted') 284 if self.__packaging == "folder": 285 cache_ts = self.__parts_ts.get(path, -1) 286 current_ts = self._get_folder_part_timestamp(path) 287 if current_ts != cache_ts: 288 part, timestamp = self._get_folder_part(path) 289 self.__parts[path] = part 290 self.__parts_ts[path] = timestamp 291 return part 292 if self.__packaging == "zip": 293 return self._get_zip_part(path) 294 if self.__packaging == "folder": 295 part, timestamp = self._get_folder_part(path) 296 self.__parts[path] = part 297 self.__parts_ts[path] = timestamp 298 return part 299 return None 300 301 @property 302 def mimetype(self) -> str: 303 """Return str value of mimetype of the document.""" 304 with contextlib.suppress(Exception): 305 b_mimetype = self.get_part("mimetype") 306 if isinstance(b_mimetype, bytes): 307 return bytes_to_str(b_mimetype) 308 return "" 309 310 @mimetype.setter 311 def mimetype(self, mimetype: str | bytes) -> None: 312 """Set mimetype value of the document.""" 313 if isinstance(mimetype, str): 314 self.__parts["mimetype"] = str_to_bytes(mimetype) 315 elif isinstance(mimetype, bytes): 316 self.__parts["mimetype"] = mimetype 317 else: 318 raise TypeError(f'Wrong mimetype "{mimetype!r}"') 319 320 def set_part(self, path: str, data: bytes) -> None: 321 """Replace or add a new part.""" 322 self.__parts[path] = data 323 324 def del_part(self, path: str) -> None: 325 """Mark a part for deletion.""" 326 self.__parts[path] = None 327 328 @property 329 def clone(self) -> Container: 330 """Make a copy of this container with no path.""" 331 if self.path and self.__packaging == "zip": 332 self._get_all_zip_part() 333 clone = deepcopy(self) 334 clone.path = None 335 return clone 336 337 @staticmethod 338 def _do_backup(target: str | Path) -> None: 339 path = Path(target) 340 if not path.exists(): 341 return 342 back_file = Path(path.stem + ".backup" + path.suffix) 343 if back_file.is_dir(): 344 try: 345 shutil.rmtree(back_file) 346 except OSError as e: 347 printwarn(str(e)) 348 try: 349 shutil.move(target, back_file) 350 except OSError as e: 351 printwarn(str(e)) 352 353 def _save_packaging(self, packaging: str | None) -> str: 354 if not packaging: 355 packaging = self.__packaging if self.__packaging else "zip" 356 packaging = packaging.strip().lower() 357 # if packaging not in ('zip', 'flat', 'folder'): 358 if packaging not in ("zip", "folder"): 359 raise ValueError(f'Packaging of type "{packaging}" is not supported') 360 return packaging 361 362 def _save_target(self, target: str | Path | io.BytesIO | None) -> str | io.BytesIO: 363 if target is None: 364 target = self.path 365 if isinstance(target, Path): 366 target = str(target) 367 if isinstance(target, str): 368 while target.endswith(os.sep): 369 target = target[:-1] 370 while target.endswith(".folder"): 371 target = target.split(".folder", 1)[0] 372 return target # type: ignore 373 374 def _save_as_zip(self, target: str | Path | io.BytesIO, backup: bool) -> None: 375 if isinstance(target, (str, Path)) and backup: 376 self._do_backup(target) 377 self._save_zip(target) 378 379 def _save_as_folder(self, target: str | Path, backup: bool) -> None: 380 if not isinstance(target, (str, Path)): 381 raise TypeError( 382 f"Saving in folder format requires a folder name, not '{target!r}'" 383 ) 384 if not str(target).endswith(".folder"): 385 target = str(target) + ".folder" 386 if backup: 387 self._do_backup(target) 388 else: 389 path = Path(target) 390 if path.exists(): 391 try: 392 shutil.rmtree(path) 393 except OSError as e: 394 printwarn(str(e)) 395 self._save_folder(target) 396 397 def save( 398 self, 399 target: str | Path | io.BytesIO | None, 400 packaging: str | None = None, 401 backup: bool = False, 402 ) -> None: 403 """Save the container to the given target, a path or a file-like 404 object. 405 406 Package the output document in the same format than current document, 407 unless "packaging" is different. 408 409 Arguments: 410 411 target -- str or file-like or Path 412 413 packaging -- 'zip', or for debugging purpose 'folder' 414 415 backup -- boolean 416 """ 417 parts = self.__parts 418 packaging = self._save_packaging(packaging) 419 # Load parts else they will be considered deleted 420 for path in self.get_parts(): 421 if path not in parts: 422 self.get_part(path) 423 target = self._save_target(target) 424 if packaging == "folder": 425 if isinstance(target, io.BytesIO): 426 raise TypeError( 427 "Impossible to save on io.BytesIO with 'folder' packaging" 428 ) 429 self._save_as_folder(target, backup) 430 else: 431 # default: 432 self._save_as_zip(target, backup)
Representation of the ODF file.
58 def __init__(self, path: Path | str | io.BytesIO | None = None) -> None: 59 self.__parts: dict[str, bytes | None] = {} 60 self.__parts_ts: dict[str, int] = {} 61 self.__path_like: Path | str | io.BytesIO | None = None 62 self.__packaging: str = "zip" 63 self.path: Path | None = None # or Path 64 if path: 65 self.open(path)
70 def open(self, path_or_file: Path | str | io.BytesIO) -> None: 71 """Load the content of an ODF file.""" 72 self.__path_like = path_or_file 73 if isinstance(path_or_file, (str, Path)): 74 self.path = Path(path_or_file).expanduser() 75 if not self.path.exists(): 76 raise FileNotFoundError(str(self.path)) 77 self.__path_like = self.path 78 if (self.path or isinstance(self.__path_like, io.BytesIO)) and is_zipfile( 79 self.__path_like # type: ignore 80 ): 81 self.__packaging = "zip" 82 return self._read_zip() 83 if self.path: 84 is_folder = False 85 with contextlib.suppress(OSError): 86 is_folder = self.path.is_dir() 87 if is_folder: 88 self.__packaging = "folder" 89 return self._read_folder() 90 raise TypeError(f"Document format not managed by odfdo: {type(path_or_file)}.")
Load the content of an ODF file.
260 def get_parts(self) -> list[str]: 261 """Get the list of members.""" 262 if not self.path: 263 # maybe a file like zip archive 264 return list(self.__parts.keys()) 265 if self.__packaging == "zip": 266 parts = [] 267 with ZipFile(self.path) as zf: 268 for name in zf.namelist(): 269 upath = normalize_path(name) 270 parts.append(upath) 271 return parts 272 elif self.__packaging == "folder": 273 return self._get_folder_parts() 274 else: 275 raise ValueError("Unable to provide parts of the document")
Get the list of members.
277 def get_part(self, path: str) -> str | bytes | None: 278 """Get the bytes of a part of the ODF.""" 279 path = str(path) 280 if path in self.__parts: 281 part = self.__parts[path] 282 if part is None: 283 raise ValueError(f'Part "{path}" is deleted') 284 if self.__packaging == "folder": 285 cache_ts = self.__parts_ts.get(path, -1) 286 current_ts = self._get_folder_part_timestamp(path) 287 if current_ts != cache_ts: 288 part, timestamp = self._get_folder_part(path) 289 self.__parts[path] = part 290 self.__parts_ts[path] = timestamp 291 return part 292 if self.__packaging == "zip": 293 return self._get_zip_part(path) 294 if self.__packaging == "folder": 295 part, timestamp = self._get_folder_part(path) 296 self.__parts[path] = part 297 self.__parts_ts[path] = timestamp 298 return part 299 return None
Get the bytes of a part of the ODF.
301 @property 302 def mimetype(self) -> str: 303 """Return str value of mimetype of the document.""" 304 with contextlib.suppress(Exception): 305 b_mimetype = self.get_part("mimetype") 306 if isinstance(b_mimetype, bytes): 307 return bytes_to_str(b_mimetype) 308 return ""
Return str value of mimetype of the document.
320 def set_part(self, path: str, data: bytes) -> None: 321 """Replace or add a new part.""" 322 self.__parts[path] = data
Replace or add a new part.
324 def del_part(self, path: str) -> None: 325 """Mark a part for deletion.""" 326 self.__parts[path] = None
Mark a part for deletion.
328 @property 329 def clone(self) -> Container: 330 """Make a copy of this container with no path.""" 331 if self.path and self.__packaging == "zip": 332 self._get_all_zip_part() 333 clone = deepcopy(self) 334 clone.path = None 335 return clone
Make a copy of this container with no path.
397 def save( 398 self, 399 target: str | Path | io.BytesIO | None, 400 packaging: str | None = None, 401 backup: bool = False, 402 ) -> None: 403 """Save the container to the given target, a path or a file-like 404 object. 405 406 Package the output document in the same format than current document, 407 unless "packaging" is different. 408 409 Arguments: 410 411 target -- str or file-like or Path 412 413 packaging -- 'zip', or for debugging purpose 'folder' 414 415 backup -- boolean 416 """ 417 parts = self.__parts 418 packaging = self._save_packaging(packaging) 419 # Load parts else they will be considered deleted 420 for path in self.get_parts(): 421 if path not in parts: 422 self.get_part(path) 423 target = self._save_target(target) 424 if packaging == "folder": 425 if isinstance(target, io.BytesIO): 426 raise TypeError( 427 "Impossible to save on io.BytesIO with 'folder' packaging" 428 ) 429 self._save_as_folder(target, backup) 430 else: 431 # default: 432 self._save_as_zip(target, backup)
Save the container to the given target, a path or a file-like object.
Package the output document in the same format than current document, unless "packaging" is different.
Arguments:
target -- str or file-like or Path
packaging -- 'zip', or for debugging purpose 'folder'
backup -- boolean
34class Content(XmlPart): 35 @property 36 def body(self) -> Element: 37 body = self.root.document_body 38 if not isinstance(body, Element): 39 raise ValueError("No body found in document") # noqa:TRY004 40 return body 41 42 # The following two seem useless but they match styles API 43 44 def _get_style_contexts(self, family: str | None) -> tuple: 45 if family == "font-face": 46 return (self.get_element("//office:font-face-decls"),) 47 return ( 48 self.get_element("//office:font-face-decls"), 49 self.get_element("//office:automatic-styles"), 50 ) 51 52 def __str__(self) -> str: 53 return str(self.body) 54 55 # Public API 56 57 def get_styles(self, family: str | None = None) -> list[Style]: 58 """Return the list of styles in the Content part, optionally limited 59 to the given family. 60 61 Arguments: 62 63 family -- str or None 64 65 Return: list of Style 66 """ 67 result: list[Style] = [] 68 for context in self._get_style_contexts(family): 69 if context is None: 70 continue 71 result.extend(context.get_styles(family=family)) 72 return result 73 74 def get_style( 75 self, 76 family: str, 77 name_or_element: str | Element | None = None, 78 display_name: str | None = None, 79 ) -> Style | None: 80 """Return the style uniquely identified by the name/family pair. If 81 the argument is already a style object, it will return it. 82 83 If the name is None, the default style is fetched. 84 85 If the name is not the internal name but the name you gave in the 86 desktop application, use display_name instead. 87 88 Arguments: 89 90 family -- 'paragraph', 'text', 'graphic', 'table', 'list', 91 'number' 92 93 name_or_element -- str or Style 94 95 display_name -- str 96 97 Return: Style or None if not found 98 """ 99 for context in self._get_style_contexts(family): 100 if context is None: 101 continue 102 style = context.get_style( 103 family, 104 name_or_element=name_or_element, 105 display_name=display_name, 106 ) 107 if style is not None: 108 return style 109 return None
Representation of an XML part.
Abstraction of the XML library behind.
57 def get_styles(self, family: str | None = None) -> list[Style]: 58 """Return the list of styles in the Content part, optionally limited 59 to the given family. 60 61 Arguments: 62 63 family -- str or None 64 65 Return: list of Style 66 """ 67 result: list[Style] = [] 68 for context in self._get_style_contexts(family): 69 if context is None: 70 continue 71 result.extend(context.get_styles(family=family)) 72 return result
Return the list of styles in the Content part, optionally limited to the given family.
Arguments:
family -- str or None
Return: list of Style
74 def get_style( 75 self, 76 family: str, 77 name_or_element: str | Element | None = None, 78 display_name: str | None = None, 79 ) -> Style | None: 80 """Return the style uniquely identified by the name/family pair. If 81 the argument is already a style object, it will return it. 82 83 If the name is None, the default style is fetched. 84 85 If the name is not the internal name but the name you gave in the 86 desktop application, use display_name instead. 87 88 Arguments: 89 90 family -- 'paragraph', 'text', 'graphic', 'table', 'list', 91 'number' 92 93 name_or_element -- str or Style 94 95 display_name -- str 96 97 Return: Style or None if not found 98 """ 99 for context in self._get_style_contexts(family): 100 if context is None: 101 continue 102 style = context.get_style( 103 family, 104 name_or_element=name_or_element, 105 display_name=display_name, 106 ) 107 if style is not None: 108 return style 109 return None
Return the style uniquely identified by the name/family pair. If the argument is already a style object, it will return it.
If the name is None, the default style is fetched.
If the name is not the internal name but the name you gave in the desktop application, use display_name instead.
Arguments:
family -- 'paragraph', 'text', 'graphic', 'table', 'list',
'number'
name_or_element -- str or Style
display_name -- str
Return: Style or None if not found
Inherited Members
159class Document: 160 """Abstraction of the ODF document. 161 162 To create a new Document, several possibilities: 163 164 - Document() or Document("text") -> an "empty" document of type text 165 - Document("spreadsheet") -> an "empty" document of type spreadsheet 166 - Document("presentation") -> an "empty" document of type presentation 167 - Document("drawing") -> an "empty" document of type drawing 168 169 Meaning of “empty”: these documents are copies of the default 170 templates documents provided with this library, which, as templates, 171 are not really empty. It may be useful to clear the newly created 172 document: document.body.clear(), or adjust meta informations like 173 description or default language: document.meta.set_language('fr-FR') 174 175 If the argument is not a known template type, or is a Path, 176 Document(file) will load the content of the ODF file. 177 178 To explicitly create a document from a custom template, use the 179 Document.new(path) method whose argument is the path to the template file. 180 """ 181 182 def __init__( 183 self, 184 target: str | bytes | Path | Container | io.BytesIO | None = "text", 185 ) -> None: 186 # Cache of XML parts 187 self.__xmlparts: dict[str, XmlPart] = {} 188 # Cache of the body 189 self.__body: Element | None = None 190 self.container: Container | None = None 191 if isinstance(target, bytes): 192 # eager conversion 193 target = bytes_to_str(target) 194 if target is None: 195 # empty document, you probably don't wnat this. 196 self.container = Container() 197 return 198 if isinstance(target, Path): 199 # let's assume we open a container on existing file 200 self.container = Container(target) 201 return 202 if isinstance(target, Container): 203 # special internal case, use an existing container 204 self.container = target 205 return 206 if isinstance(target, str): 207 if target in ODF_TEMPLATES: 208 # assuming a new document from templates 209 self.container = container_from_template(target) 210 return 211 # let's assume we open a container on existing file 212 self.container = Container(target) 213 return 214 if isinstance(target, io.BytesIO): 215 self.container = Container(target) 216 return 217 raise TypeError(f"Unknown Document source type: '{target!r}'") 218 219 def __repr__(self) -> str: 220 return f"<{self.__class__.__name__} type={self.get_type()} path={self.path}>" 221 222 def __str__(self) -> str: 223 try: 224 return str(self.get_formatted_text()) 225 except NotImplementedError: 226 return self.body.text_recursive 227 228 @classmethod 229 def new(cls, template: str | Path | io.BytesIO = "text") -> Document: 230 """Create a Document from a template. 231 232 The template argument is expected to be the path to a ODF template. 233 234 Arguments: 235 236 template -- str or Path or file-like (io.BytesIO) 237 238 Return : ODF document -- Document 239 """ 240 container = container_from_template(template) 241 return cls(container) 242 243 # Public API 244 245 @property 246 def path(self) -> Path | None: 247 """Shortcut to Document.Container.path.""" 248 if not self.container: 249 return None 250 return self.container.path 251 252 @path.setter 253 def path(self, path_or_str: str | Path) -> None: 254 """Shortcut to Document.Container.path 255 256 Only accepting str or Path.""" 257 if not self.container: 258 return 259 self.container.path = Path(path_or_str) 260 261 def get_parts(self) -> list[str]: 262 """Return available part names with path inside the archive, e.g. 263 ['content.xml', ..., 'Pictures/100000000000032000000258912EB1C3.jpg'] 264 """ 265 if not self.container: 266 raise ValueError("Empty Container") 267 return self.container.get_parts() 268 269 def get_part(self, path: str) -> XmlPart | str | bytes | None: 270 """Return the bytes of the given part. The path is relative to the 271 archive, e.g. "Pictures/1003200258912EB1C3.jpg". 272 273 'content', 'meta', 'settings', 'styles' and 'manifest' are shortcuts 274 to the real path, e.g. content.xml, and return a dedicated object with 275 its own API. 276 277 path formated as URI, so always use '/' separator 278 """ 279 if not self.container: 280 raise ValueError("Empty Container") 281 # "./ObjectReplacements/Object 1" 282 path = path.lstrip("./") 283 path = _get_part_path(path) 284 cls = _get_part_class(path) 285 # Raw bytes 286 if cls is None: 287 return self.container.get_part(path) 288 # XML part 289 part = self.__xmlparts.get(path) 290 if part is None: 291 self.__xmlparts[path] = part = cls(path, self.container) 292 return part 293 294 def set_part(self, path: str, data: bytes) -> None: 295 """Set the bytes of the given part. The path is relative to the 296 archive, e.g. "Pictures/1003200258912EB1C3.jpg". 297 298 path formated as URI, so always use '/' separator 299 """ 300 if not self.container: 301 raise ValueError("Empty Container") 302 # "./ObjectReplacements/Object 1" 303 path = path.lstrip("./") 304 path = _get_part_path(path) 305 cls = _get_part_class(path) 306 # XML part overwritten 307 if cls is not None: 308 with suppress(KeyError): 309 self.__xmlparts[path] 310 self.container.set_part(path, data) 311 312 def del_part(self, path: str) -> None: 313 """Mark a part for deletion. The path is relative to the archive, 314 e.g. "Pictures/1003200258912EB1C3.jpg" 315 """ 316 if not self.container: 317 raise ValueError("Empty Container") 318 path = _get_part_path(path) 319 cls = _get_part_class(path) 320 if path == ODF_MANIFEST or cls is not None: 321 raise ValueError(f"part '{path}' is mandatory") 322 self.container.del_part(path) 323 324 @property 325 def mimetype(self) -> str: 326 if not self.container: 327 raise ValueError("Empty Container") 328 return self.container.mimetype 329 330 @mimetype.setter 331 def mimetype(self, mimetype: str) -> None: 332 if not self.container: 333 raise ValueError("Empty Container") 334 self.container.mimetype = mimetype 335 336 def get_type(self) -> str: 337 """Get the ODF type (also called class) of this document. 338 339 Return: 'chart', 'database', 'formula', 'graphics', 340 'graphics-template', 'image', 'presentation', 341 'presentation-template', 'spreadsheet', 'spreadsheet-template', 342 'text', 'text-master', 'text-template' or 'text-web' 343 """ 344 # The mimetype must be with the form: 345 # application/vnd.oasis.opendocument.text 346 347 # Isolate and return the last part 348 return self.mimetype.rsplit(".", 1)[-1] 349 350 @property 351 def body(self) -> Element: 352 """Return the body element of the content part, where actual content 353 is stored. 354 """ 355 if self.__body is None: 356 self.__body = self.content.body 357 return self.__body 358 359 @property 360 def meta(self) -> Meta: 361 """Return the meta part (meta.xml) of the document, where meta data 362 are stored.""" 363 metadata = self.get_part(ODF_META) 364 if metadata is None or not isinstance(metadata, Meta): 365 raise ValueError("Empty Meta") 366 return metadata 367 368 @property 369 def manifest(self) -> Manifest: 370 """Return the manifest part (manifest.xml) of the document.""" 371 manifest = self.get_part(ODF_MANIFEST) 372 if manifest is None or not isinstance(manifest, Manifest): 373 raise ValueError("Empty Manifest") 374 return manifest 375 376 def _get_formatted_text_footnotes( 377 self, 378 result: list[str], 379 context: dict, 380 rst_mode: bool, 381 ) -> None: 382 # Separate text from notes 383 if rst_mode: 384 result.append("\n") 385 else: 386 result.append("----\n") 387 for citation, body in context["footnotes"]: 388 if rst_mode: 389 result.append(f".. [#] {body}\n") 390 else: 391 result.append(f"[{citation}] {body}\n") 392 # Append a \n after the notes 393 result.append("\n") 394 # Reset for the next paragraph 395 context["footnotes"] = [] 396 397 def _get_formatted_text_annotations( 398 self, 399 result: list[str], 400 context: dict, 401 rst_mode: bool, 402 ) -> None: 403 # Insert the annotations 404 # With a separation 405 if rst_mode: 406 result.append("\n") 407 else: 408 result.append("----\n") 409 for annotation in context["annotations"]: 410 if rst_mode: 411 result.append(f".. [#] {annotation}\n") 412 else: 413 result.append(f"[*] {annotation}\n") 414 context["annotations"] = [] 415 416 def _get_formatted_text_images( 417 self, 418 result: list[str], 419 context: dict, 420 rst_mode: bool, 421 ) -> None: 422 # Insert the images ref, only in rst mode 423 result.append("\n") 424 for ref, filename, (width, height) in context["images"]: 425 result.append(f".. {ref} image:: {filename}\n") 426 if width is not None: 427 result.append(f" :width: {width}\n") 428 if height is not None: 429 result.append(f" :height: {height}\n") 430 context["images"] = [] 431 432 def _get_formatted_text_endnotes( 433 self, 434 result: list[str], 435 context: dict, 436 rst_mode: bool, 437 ) -> None: 438 # Append the end notes 439 if rst_mode: 440 result.append("\n\n") 441 else: 442 result.append("\n========\n") 443 for citation, body in context["endnotes"]: 444 if rst_mode: 445 result.append(f".. [*] {body}\n") 446 else: 447 result.append(f"({citation}) {body}\n") 448 449 def get_formatted_text(self, rst_mode: bool = False) -> str: 450 """Return content as text, with some formatting.""" 451 # For the moment, only "type='text'" 452 doc_type = self.get_type() 453 if doc_type == "spreadsheet": 454 return self._tables_csv() 455 if doc_type in { 456 "text", 457 "text-template", 458 "presentation", 459 "presentation-template", 460 }: 461 return self._formatted_text(rst_mode) 462 raise NotImplementedError(f"Type of document '{doc_type}' not supported yet") 463 464 def _tables_csv(self) -> str: 465 return "\n\n".join(str(table) for table in self.body.get_tables()) 466 467 def _formatted_text(self, rst_mode: bool) -> str: 468 # Initialize an empty context 469 context = { 470 "document": self, 471 "footnotes": [], 472 "endnotes": [], 473 "annotations": [], 474 "rst_mode": rst_mode, 475 "img_counter": 0, 476 "images": [], 477 "no_img_level": 0, 478 } 479 body = self.body 480 # Get the text 481 result = [] 482 for child in body.children: 483 # self._get_formatted_text_child(result, element, context, rst_mode) 484 # if child.tag == "table:table": 485 # result.append(child.get_formatted_text(context)) 486 # return 487 result.append(child.get_formatted_text(context)) 488 if context["footnotes"]: 489 self._get_formatted_text_footnotes(result, context, rst_mode) 490 if context["annotations"]: 491 self._get_formatted_text_annotations(result, context, rst_mode) 492 # Insert the images ref, only in rst mode 493 if context["images"]: 494 self._get_formatted_text_images(result, context, rst_mode) 495 if context["endnotes"]: 496 self._get_formatted_text_endnotes(result, context, rst_mode) 497 return "".join(result) 498 499 def get_formated_meta(self) -> str: 500 """Return meta informations as text, with some formatting.""" 501 result: list[str] = [] 502 503 # Simple values 504 def print_info(name: str, value: Any) -> None: 505 if value: 506 result.append(f"{name}: {value}") 507 508 meta = self.meta 509 print_info("Title", meta.get_title()) 510 print_info("Subject", meta.get_subject()) 511 print_info("Language", meta.get_language()) 512 print_info("Modification date", meta.get_modification_date()) 513 print_info("Creation date", meta.get_creation_date()) 514 print_info("Initial creator", meta.get_initial_creator()) 515 print_info("Keyword", meta.get_keywords()) 516 print_info("Editing duration", meta.get_editing_duration()) 517 print_info("Editing cycles", meta.get_editing_cycles()) 518 print_info("Generator", meta.get_generator()) 519 520 # Statistic 521 result.append("Statistic:") 522 statistic = meta.get_statistic() 523 if statistic: 524 for name, data in statistic.items(): 525 result.append(f" - {name[5:].replace('-', ' ').capitalize()}: {data}") 526 527 # User defined metadata 528 result.append("User defined metadata:") 529 user_metadata = meta.get_user_defined_metadata() 530 for name, data2 in user_metadata.items(): 531 result.append(f" - {name}: {data2}") 532 533 # And the description 534 print_info("Description", meta.get_description()) 535 536 return "\n".join(result) + "\n" 537 538 def add_file(self, path_or_file: str | Path) -> str: 539 """Insert a file from a path or a file-like object in the container. 540 541 Return the full path to reference in the content. 542 543 Arguments: 544 545 path_or_file -- str or Path or file-like 546 547 Return: str (URI) 548 """ 549 if not self.container: 550 raise ValueError("Empty Container") 551 name = "" 552 # Folder for added files (FIXME hard-coded and copied) 553 manifest = self.manifest 554 medias = manifest.get_paths() 555 # uuid = str(uuid4()) 556 557 if isinstance(path_or_file, (str, Path)): 558 path = Path(path_or_file) 559 extension = path.suffix.lower() 560 name = f"{path.stem}{extension}" 561 if posixpath.join("Pictures", name) in medias: 562 name = f"{path.stem}_{uuid4()}{extension}" 563 else: 564 path = None 565 name = getattr(path_or_file, "name", None) 566 if not name: 567 name = str(uuid4()) 568 media_type, _encoding = guess_type(name) 569 if not media_type: 570 media_type = "application/octet-stream" 571 if manifest.get_media_type("Pictures/") is None: 572 manifest.add_full_path("Pictures/") 573 full_path = posixpath.join("Pictures", name) 574 if path is None: 575 self.container.set_part(full_path, path_or_file.read()) # type:ignore 576 else: 577 self.container.set_part(full_path, path.read_bytes()) 578 manifest.add_full_path(full_path, media_type) 579 return full_path 580 581 @property 582 def clone(self) -> Document: 583 """Return an exact copy of the document. 584 585 Return: Document 586 """ 587 clone = object.__new__(self.__class__) 588 for name in self.__dict__: 589 if name == "_Document__body": 590 setattr(clone, name, None) 591 elif name == "_Document__xmlparts": 592 setattr(clone, name, {}) 593 elif name == "container": 594 if not self.container: 595 raise ValueError("Empty Container") 596 setattr(clone, name, self.container.clone) 597 else: 598 value = deepcopy(getattr(self, name)) 599 setattr(clone, name, value) 600 return clone 601 602 def save( 603 self, 604 target: str | Path | io.BytesIO | None = None, 605 packaging: str = ZIP, 606 pretty: bool = False, 607 backup: bool = False, 608 ) -> None: 609 """Save the document, at the same place it was opened or at the given 610 target path. Target can also be a file-like object. It can be saved 611 as a Zip file (default), flat XML format or as files in a folder 612 (for debugging purpose). XML parts can be pretty printed. 613 614 Arguments: 615 616 target -- str or file-like object 617 618 packaging -- 'zip', 'folder', 'xml' 619 620 pretty -- bool 621 622 backup -- bool 623 """ 624 if not self.container: 625 raise ValueError("Empty Container") 626 # Some advertising 627 self.meta.set_generator_default() 628 # Synchronize data with container 629 container = self.container 630 for path, part in self.__xmlparts.items(): 631 if part is not None: 632 container.set_part(path, part.serialize(pretty)) 633 # Save the container 634 container.save(target, packaging=packaging, backup=backup) 635 636 @property 637 def content(self) -> Content: 638 content: Content | None = self.get_part(ODF_CONTENT) # type:ignore 639 if content is None: 640 raise ValueError("Empty Content") 641 return content 642 643 @property 644 def styles(self) -> Styles: 645 styles: Styles | None = self.get_part(ODF_STYLES) # type:ignore 646 if styles is None: 647 raise ValueError("Empty Styles") 648 return styles 649 650 # Styles over several parts 651 652 def get_styles( 653 self, 654 family: str | bytes = "", 655 automatic: bool = False, 656 ) -> list[Style | Element]: 657 # compatibility with old versions: 658 659 if isinstance(family, bytes): 660 family = bytes_to_str(family) 661 return self.content.get_styles(family=family) + self.styles.get_styles( 662 family=family, automatic=automatic 663 ) 664 665 def get_style( 666 self, 667 family: str, 668 name_or_element: str | Style | None = None, 669 display_name: str | None = None, 670 ) -> Style | None: 671 """Return the style uniquely identified by the name/family pair. If 672 the argument is already a style object, it will return it. 673 674 If the name is None, the default style is fetched. 675 676 If the name is not the internal name but the name you gave in a 677 desktop application, use display_name instead. 678 679 Arguments: 680 681 family -- 'paragraph', 'text', 'graphic', 'table', 'list', 682 'number', 'page-layout', 'master-page' 683 684 name -- str or Element or None 685 686 display_name -- str 687 688 Return: Style or None if not found. 689 """ 690 # 1. content.xml 691 element = self.content.get_style( 692 family, name_or_element=name_or_element, display_name=display_name 693 ) 694 if element is not None: 695 return element 696 # 2. styles.xml 697 return self.styles.get_style( 698 family, 699 name_or_element=name_or_element, 700 display_name=display_name, 701 ) 702 703 @staticmethod 704 def _pseudo_style_attribute(style_element: Style | Element, attribute: str) -> Any: 705 if hasattr(style_element, attribute): 706 return getattr(style_element, attribute) 707 return "" 708 709 def _set_automatic_name(self, style: Style, family: str) -> None: 710 """Generate a name for the new automatic style.""" 711 if not hasattr(style, "name"): 712 # do nothing 713 return 714 styles = self.get_styles(family=family, automatic=True) 715 max_index = 0 716 for existing_style in styles: 717 if not hasattr(existing_style, "name"): 718 continue 719 if not existing_style.name.startswith(AUTOMATIC_PREFIX): 720 continue 721 try: 722 index = int(existing_style.name[len(AUTOMATIC_PREFIX) :]) # type: ignore 723 except ValueError: 724 continue 725 max_index = max(max_index, index) 726 727 style.name = f"{AUTOMATIC_PREFIX}{max_index+1}" 728 729 def _insert_style_get_common_styles( 730 self, 731 family: str, 732 name: str, 733 ) -> tuple[Any, Any]: 734 style_container = self.styles.get_element("office:styles") 735 existing = self.styles.get_style(family, name) 736 return existing, style_container 737 738 def _insert_style_get_automatic_styles( 739 self, 740 style: Style, 741 family: str, 742 name: str, 743 ) -> tuple[Any, Any]: 744 style_container = self.content.get_element("office:automatic-styles") 745 # A name ? 746 if name: 747 if hasattr(style, "name"): 748 style.name = name 749 existing = self.content.get_style(family, name) 750 else: 751 self._set_automatic_name(style, family) 752 existing = None 753 return existing, style_container 754 755 def _insert_style_get_default_styles( 756 self, 757 style: Style, 758 family: str, 759 name: str, 760 ) -> tuple[Any, Any]: 761 style_container = self.styles.get_element("office:styles") 762 style.tag = "style:default-style" 763 if name: 764 style.del_attribute("style:name") 765 existing = self.styles.get_style(family) 766 return existing, style_container 767 768 def _insert_style_get_master_page( 769 self, 770 family: str, 771 name: str, 772 ) -> tuple[Any, Any]: 773 style_container = self.styles.get_element("office:master-styles") 774 existing = self.styles.get_style(family, name) 775 return existing, style_container 776 777 def _insert_style_get_font_face_default( 778 self, 779 family: str, 780 name: str, 781 ) -> tuple[Any, Any]: 782 style_container = self.styles.get_element("office:font-face-decls") 783 existing = self.styles.get_style(family, name) 784 return existing, style_container 785 786 def _insert_style_get_font_face( 787 self, 788 family: str, 789 name: str, 790 ) -> tuple[Any, Any]: 791 style_container = self.content.get_element("office:font-face-decls") 792 existing = self.content.get_style(family, name) 793 return existing, style_container 794 795 def _insert_style_get_page_layout( 796 self, 797 family: str, 798 name: str, 799 ) -> tuple[Any, Any]: 800 # force to automatic 801 style_container = self.styles.get_element("office:automatic-styles") 802 existing = self.styles.get_style(family, name) 803 return existing, style_container 804 805 def _insert_style_get_draw_fill_image( 806 self, 807 name: str, 808 ) -> tuple[Any, Any]: 809 # special case for 'draw:fill-image' pseudo style 810 # not family and style_element.__class__.__name__ == "DrawFillImage" 811 style_container = self.styles.get_element("office:styles") 812 existing = self.styles.get_style("", name) 813 return existing, style_container 814 815 def _insert_style_standard( 816 self, 817 style: Style, 818 name: str, 819 family: str, 820 automatic: bool, 821 default: bool, 822 ) -> tuple[Any, Any]: 823 # Common style 824 if name and automatic is False and default is False: 825 return self._insert_style_get_common_styles(family, name) 826 # Automatic style 827 elif automatic is True and default is False: 828 return self._insert_style_get_automatic_styles(style, family, name) 829 # Default style 830 elif automatic is False and default is True: 831 return self._insert_style_get_default_styles(style, family, name) 832 else: 833 raise AttributeError("Invalid combination of arguments") 834 835 def insert_style( # noqa: C901 836 self, 837 style: Style | str, 838 name: str = "", 839 automatic: bool = False, 840 default: bool = False, 841 ) -> Any: 842 """Insert the given style object in the document, as required by the 843 style family and type. 844 845 The style is expected to be a common style with a name. In case it 846 was created with no name, the given can be set on the fly. 847 848 If automatic is True, the style will be inserted as an automatic 849 style. 850 851 If default is True, the style will be inserted as a default style and 852 would replace any existing default style of the same family. Any name 853 or display name would be ignored. 854 855 Automatic and default arguments are mutually exclusive. 856 857 All styles can't be used as default styles. Default styles are 858 allowed for the following families: paragraph, text, section, table, 859 table-column, table-row, table-cell, table-page, chart, drawing-page, 860 graphic, presentation, control and ruby. 861 862 Arguments: 863 864 style -- Style or str 865 866 name -- str 867 868 automatic -- bool 869 870 default -- bool 871 872 Return : style name -- str 873 """ 874 875 # if style is a str, assume it is the Style definition 876 if isinstance(style, str): 877 style_element: Style = Element.from_tag(style) # type: ignore 878 else: 879 style_element = style 880 if not isinstance(style_element, Element): 881 raise TypeError(f"Unknown Style type: '{style!r}'") 882 883 # Get family and name 884 family = self._pseudo_style_attribute(style_element, "family") 885 if not name: 886 name = self._pseudo_style_attribute(style_element, "name") 887 888 # Master page style 889 if family == "master-page": 890 existing, style_container = self._insert_style_get_master_page(family, name) 891 # Font face declarations 892 elif family == "font-face": 893 if default: 894 existing, style_container = self._insert_style_get_font_face_default( 895 family, name 896 ) 897 else: 898 existing, style_container = self._insert_style_get_font_face( 899 family, name 900 ) 901 # page layout style 902 elif family == "page-layout": 903 existing, style_container = self._insert_style_get_page_layout(family, name) 904 # Common style 905 elif family in FAMILY_ODF_STD or family in {"number"}: 906 existing, style_container = self._insert_style_standard( 907 style_element, name, family, automatic, default 908 ) 909 elif not family and style_element.__class__.__name__ == "DrawFillImage": 910 # special case for 'draw:fill-image' pseudo style 911 existing, style_container = self._insert_style_get_draw_fill_image(name) 912 # Invalid style 913 else: 914 raise ValueError( 915 "Invalid style: " 916 f"{style_element}, tag:{style_element.tag}, family:{family}" 917 ) 918 919 # Insert it! 920 if existing is not None: 921 style_container.delete(existing) 922 style_container.append(style_element) 923 return self._pseudo_style_attribute(style_element, "name") 924 925 def get_styled_elements(self, name: str = "") -> list[Element]: 926 """Brute-force to find paragraphs, tables, etc. using the given style 927 name (or all by default). 928 929 Arguments: 930 931 name -- str 932 933 Return: list 934 """ 935 # Header, footer, etc. have styles too 936 return self.content.root.get_styled_elements( 937 name 938 ) + self.styles.root.get_styled_elements(name) 939 940 def show_styles( 941 self, 942 automatic: bool = True, 943 common: bool = True, 944 properties: bool = False, 945 ) -> str: 946 infos = [] 947 for style in self.get_styles(): 948 try: 949 name = style.name # type: ignore 950 except AttributeError: 951 print("--------------") 952 print(style.__class__) 953 print(style.serialize()) 954 raise 955 if style.__class__.__name__ == "DrawFillImage": 956 family = "" 957 else: 958 family = str(style.family) # type: ignore 959 parent = style.parent 960 is_auto = parent and parent.tag == "office:automatic-styles" 961 if is_auto and automatic is False or not is_auto and common is False: 962 continue 963 is_used = bool(self.get_styled_elements(name)) 964 infos.append( 965 { 966 "type": "auto " if is_auto else "common", 967 "used": "y" if is_used else "n", 968 "family": family, 969 "parent": self._pseudo_style_attribute(style, "parent_style") or "", 970 "name": name or "", 971 "display_name": self._pseudo_style_attribute(style, "display_name") 972 or "", 973 "properties": style.get_properties() if properties else None, # type: ignore 974 } 975 ) 976 if not infos: 977 return "" 978 # Sort by family and name 979 infos.sort(key=itemgetter("family", "name")) 980 # Show common and used first 981 infos.sort(key=itemgetter("type", "used"), reverse=True) 982 max_family = str(max([len(x["family"]) for x in infos])) # type: ignore 983 max_parent = str(max([len(x["parent"]) for x in infos])) # type: ignore 984 formater = ( 985 "%(type)s used:%(used)s family:%(family)-0" 986 + max_family 987 + "s parent:%(parent)-0" 988 + max_parent 989 + "s name:%(name)s" 990 ) 991 output = [] 992 for info in infos: 993 line = formater % info 994 if info["display_name"]: 995 line += " display_name:" + info["display_name"] # type: ignore 996 output.append(line) 997 if info["properties"]: 998 for name, value in info["properties"].items(): # type: ignore 999 output.append(f" - {name}: {value}") 1000 output.append("") 1001 return "\n".join(output) 1002 1003 def delete_styles(self) -> int: 1004 """Remove all style information from content and all styles. 1005 1006 Return: number of deleted styles 1007 """ 1008 # First remove references to styles 1009 for element in self.get_styled_elements(): 1010 for attribute in ( 1011 "text:style-name", 1012 "draw:style-name", 1013 "draw:text-style-name", 1014 "table:style-name", 1015 "style:page-layout-name", 1016 ): 1017 try: 1018 element.del_attribute(attribute) 1019 except KeyError: 1020 continue 1021 # Then remove supposedly orphaned styles 1022 deleted = 0 1023 for style in self.get_styles(): 1024 if style.name is None: # type: ignore 1025 # Don't delete default styles 1026 continue 1027 # elif type(style) is odf_master_page: 1028 # # Don't suppress header and footer, just styling was removed 1029 # continue 1030 style.delete() 1031 deleted += 1 1032 return deleted 1033 1034 def merge_styles_from(self, document: Document) -> None: 1035 """Copy all the styles of a document into ourself. 1036 1037 Styles with the same type and name will be replaced, so only unique 1038 styles will be preserved. 1039 """ 1040 manifest = self.manifest 1041 document_manifest = document.manifest 1042 for style in document.get_styles(): 1043 tagname = style.tag 1044 family = self._pseudo_style_attribute(style, "family") 1045 stylename = style.name # type: ignore 1046 container = style.parent 1047 container_name = container.tag # type: ignore 1048 partname = container.parent.tag # type: ignore 1049 # The destination part 1050 if partname == "office:document-styles": 1051 part: Content | Styles = self.styles 1052 elif partname == "office:document-content": 1053 part = self.content 1054 else: 1055 raise NotImplementedError(partname) 1056 # Implemented containers 1057 if container_name not in { 1058 "office:styles", 1059 "office:automatic-styles", 1060 "office:master-styles", 1061 "office:font-face-decls", 1062 }: 1063 raise NotImplementedError(container_name) 1064 dest = part.get_element(f"//{container_name}") 1065 # Implemented style types 1066 # if tagname not in registered_styles: 1067 # raise NotImplementedError(tagname) 1068 duplicate = part.get_style(family, stylename) 1069 if duplicate is not None: 1070 duplicate.delete() 1071 dest.append(style) 1072 # Copy images from the header/footer 1073 if tagname == "style:master-page": 1074 query = "descendant::draw:image" 1075 for image in style.get_elements(query): 1076 url = image.url # type: ignore 1077 part_url = document.get_part(url) 1078 # Manually add the part to keep the name 1079 self.set_part(url, part_url) # type: ignore 1080 media_type = document_manifest.get_media_type(url) 1081 manifest.add_full_path(url, media_type) # type: ignore 1082 # Copy images from the fill-image 1083 elif tagname == "draw:fill-image": 1084 url = style.url # type: ignore 1085 part_url = document.get_part(url) 1086 self.set_part(url, part_url) # type: ignore 1087 media_type = document_manifest.get_media_type(url) 1088 manifest.add_full_path(url, media_type) # type: ignore 1089 1090 def add_page_break_style(self) -> None: 1091 """Ensure that the document contains the style required for a manual page break. 1092 1093 Then a manual page break can be added to the document with: 1094 from paragraph import PageBreak 1095 ... 1096 document.body.append(PageBreak()) 1097 1098 Note: this style uses the property 'fo:break-after', another 1099 possibility could be the property 'fo:break-before' 1100 """ 1101 if existing := self.get_style( # noqa: SIM102 1102 family="paragraph", 1103 name_or_element="odfdopagebreak", 1104 ): 1105 if properties := existing.get_properties(): # noqa: SIM102 1106 if properties["fo:break-after"] == "page": 1107 return 1108 style = ( 1109 '<style:style style:family="paragraph" style:parent-style-name="Standard" ' 1110 'style:name="odfdopagebreak">' 1111 '<style:paragraph-properties fo:break-after="page"/></style:style>' 1112 ) 1113 self.insert_style(style, automatic=False) 1114 1115 def get_style_properties( 1116 self, family: str, name: str, area: str | None = None 1117 ) -> dict[str, str] | None: 1118 """Return the properties of the required style as a dict.""" 1119 style = self.get_style(family, name) 1120 if style is None: 1121 return None 1122 return style.get_properties(area=area) # type: ignore 1123 1124 def get_cell_style_properties( # noqa: C901 1125 self, table: str | int, coord: tuple | list | str 1126 ) -> dict[str, str]: # type: ignore 1127 """Return the style properties of a table cell of a .ods document, 1128 from the cell style or from the row style.""" 1129 1130 def _get_table(table: int | str) -> Table | None: 1131 table_pos = 0 1132 table_name = None 1133 if isinstance(table, int): 1134 table_pos = table 1135 elif isinstance(table, str): 1136 table_name = table_name 1137 else: 1138 raise TypeError(f"Table parameter must be int or str: {table!r}") 1139 return self.body.get_table( 1140 position=table_pos, name=table_name # type: ignore 1141 ) 1142 1143 if not (sheet := _get_table(table)): 1144 return {} 1145 cell = sheet.get_cell(coord, clone=False) 1146 if cell.style: 1147 return ( 1148 self.get_style_properties("table-cell", cell.style, "table-cell") or {} 1149 ) 1150 try: 1151 row = sheet.get_row(cell.y, clone=False, create=False) # type: ignore 1152 if row.style: # noqa: SIM102 1153 if props := self.get_style_properties( 1154 "table-row", row.style, "table-cell" 1155 ): 1156 return props 1157 column = sheet.get_column(cell.x) # type: ignore 1158 style = column.get_default_cell_style() 1159 if style: # noqa: SIM102 1160 if props := self.get_style_properties( 1161 "table-cell", style, "table-cell" 1162 ): 1163 return props 1164 except ValueError: 1165 pass 1166 return {} 1167 1168 def get_cell_background_color( 1169 self, 1170 table: str | int, 1171 coord: tuple | list | str, 1172 default: str = "#ffffff", 1173 ) -> str: 1174 """Return the background color of a table cell of a .ods document, 1175 from the cell style, or from the row or column. 1176 1177 If color is not defined, return default value..""" 1178 found = self.get_cell_style_properties(table, coord).get("fo:background-color") 1179 return found or default
Abstraction of the ODF document.
To create a new Document, several possibilities:
- Document() or Document("text") -> an "empty" document of type text
- Document("spreadsheet") -> an "empty" document of type spreadsheet
- Document("presentation") -> an "empty" document of type presentation
- Document("drawing") -> an "empty" document of type drawing
Meaning of “empty”: these documents are copies of the default
templates documents provided with this library, which, as templates,
are not really empty. It may be useful to clear the newly created
document: document.body.clear(), or adjust meta informations like
description or default language: document.meta.set_language('fr-FR')
If the argument is not a known template type, or is a Path, Document(file) will load the content of the ODF file.
To explicitly create a document from a custom template, use the Document.new(path) method whose argument is the path to the template file.
182 def __init__( 183 self, 184 target: str | bytes | Path | Container | io.BytesIO | None = "text", 185 ) -> None: 186 # Cache of XML parts 187 self.__xmlparts: dict[str, XmlPart] = {} 188 # Cache of the body 189 self.__body: Element | None = None 190 self.container: Container | None = None 191 if isinstance(target, bytes): 192 # eager conversion 193 target = bytes_to_str(target) 194 if target is None: 195 # empty document, you probably don't wnat this. 196 self.container = Container() 197 return 198 if isinstance(target, Path): 199 # let's assume we open a container on existing file 200 self.container = Container(target) 201 return 202 if isinstance(target, Container): 203 # special internal case, use an existing container 204 self.container = target 205 return 206 if isinstance(target, str): 207 if target in ODF_TEMPLATES: 208 # assuming a new document from templates 209 self.container = container_from_template(target) 210 return 211 # let's assume we open a container on existing file 212 self.container = Container(target) 213 return 214 if isinstance(target, io.BytesIO): 215 self.container = Container(target) 216 return 217 raise TypeError(f"Unknown Document source type: '{target!r}'")
228 @classmethod 229 def new(cls, template: str | Path | io.BytesIO = "text") -> Document: 230 """Create a Document from a template. 231 232 The template argument is expected to be the path to a ODF template. 233 234 Arguments: 235 236 template -- str or Path or file-like (io.BytesIO) 237 238 Return : ODF document -- Document 239 """ 240 container = container_from_template(template) 241 return cls(container)
Create a Document from a template.
The template argument is expected to be the path to a ODF template.
Arguments:
template -- str or Path or file-like (io.BytesIO)
Return : ODF document -- Document
245 @property 246 def path(self) -> Path | None: 247 """Shortcut to Document.Container.path.""" 248 if not self.container: 249 return None 250 return self.container.path
Shortcut to Document.Container.path.
261 def get_parts(self) -> list[str]: 262 """Return available part names with path inside the archive, e.g. 263 ['content.xml', ..., 'Pictures/100000000000032000000258912EB1C3.jpg'] 264 """ 265 if not self.container: 266 raise ValueError("Empty Container") 267 return self.container.get_parts()
Return available part names with path inside the archive, e.g. ['content.xml', ..., 'Pictures/100000000000032000000258912EB1C3.jpg']
269 def get_part(self, path: str) -> XmlPart | str | bytes | None: 270 """Return the bytes of the given part. The path is relative to the 271 archive, e.g. "Pictures/1003200258912EB1C3.jpg". 272 273 'content', 'meta', 'settings', 'styles' and 'manifest' are shortcuts 274 to the real path, e.g. content.xml, and return a dedicated object with 275 its own API. 276 277 path formated as URI, so always use '/' separator 278 """ 279 if not self.container: 280 raise ValueError("Empty Container") 281 # "./ObjectReplacements/Object 1" 282 path = path.lstrip("./") 283 path = _get_part_path(path) 284 cls = _get_part_class(path) 285 # Raw bytes 286 if cls is None: 287 return self.container.get_part(path) 288 # XML part 289 part = self.__xmlparts.get(path) 290 if part is None: 291 self.__xmlparts[path] = part = cls(path, self.container) 292 return part
Return the bytes of the given part. The path is relative to the archive, e.g. "Pictures/1003200258912EB1C3.jpg".
'content', 'meta', 'settings', 'styles' and 'manifest' are shortcuts to the real path, e.g. content.xml, and return a dedicated object with its own API.
path formated as URI, so always use '/' separator
294 def set_part(self, path: str, data: bytes) -> None: 295 """Set the bytes of the given part. The path is relative to the 296 archive, e.g. "Pictures/1003200258912EB1C3.jpg". 297 298 path formated as URI, so always use '/' separator 299 """ 300 if not self.container: 301 raise ValueError("Empty Container") 302 # "./ObjectReplacements/Object 1" 303 path = path.lstrip("./") 304 path = _get_part_path(path) 305 cls = _get_part_class(path) 306 # XML part overwritten 307 if cls is not None: 308 with suppress(KeyError): 309 self.__xmlparts[path] 310 self.container.set_part(path, data)
Set the bytes of the given part. The path is relative to the archive, e.g. "Pictures/1003200258912EB1C3.jpg".
path formated as URI, so always use '/' separator
312 def del_part(self, path: str) -> None: 313 """Mark a part for deletion. The path is relative to the archive, 314 e.g. "Pictures/1003200258912EB1C3.jpg" 315 """ 316 if not self.container: 317 raise ValueError("Empty Container") 318 path = _get_part_path(path) 319 cls = _get_part_class(path) 320 if path == ODF_MANIFEST or cls is not None: 321 raise ValueError(f"part '{path}' is mandatory") 322 self.container.del_part(path)
Mark a part for deletion. The path is relative to the archive, e.g. "Pictures/1003200258912EB1C3.jpg"
336 def get_type(self) -> str: 337 """Get the ODF type (also called class) of this document. 338 339 Return: 'chart', 'database', 'formula', 'graphics', 340 'graphics-template', 'image', 'presentation', 341 'presentation-template', 'spreadsheet', 'spreadsheet-template', 342 'text', 'text-master', 'text-template' or 'text-web' 343 """ 344 # The mimetype must be with the form: 345 # application/vnd.oasis.opendocument.text 346 347 # Isolate and return the last part 348 return self.mimetype.rsplit(".", 1)[-1]
Get the ODF type (also called class) of this document.
Return: 'chart', 'database', 'formula', 'graphics', 'graphics-template', 'image', 'presentation', 'presentation-template', 'spreadsheet', 'spreadsheet-template', 'text', 'text-master', 'text-template' or 'text-web'
350 @property 351 def body(self) -> Element: 352 """Return the body element of the content part, where actual content 353 is stored. 354 """ 355 if self.__body is None: 356 self.__body = self.content.body 357 return self.__body
Return the body element of the content part, where actual content is stored.
359 @property 360 def meta(self) -> Meta: 361 """Return the meta part (meta.xml) of the document, where meta data 362 are stored.""" 363 metadata = self.get_part(ODF_META) 364 if metadata is None or not isinstance(metadata, Meta): 365 raise ValueError("Empty Meta") 366 return metadata
Return the meta part (meta.xml) of the document, where meta data are stored.
368 @property 369 def manifest(self) -> Manifest: 370 """Return the manifest part (manifest.xml) of the document.""" 371 manifest = self.get_part(ODF_MANIFEST) 372 if manifest is None or not isinstance(manifest, Manifest): 373 raise ValueError("Empty Manifest") 374 return manifest
Return the manifest part (manifest.xml) of the document.
449 def get_formatted_text(self, rst_mode: bool = False) -> str: 450 """Return content as text, with some formatting.""" 451 # For the moment, only "type='text'" 452 doc_type = self.get_type() 453 if doc_type == "spreadsheet": 454 return self._tables_csv() 455 if doc_type in { 456 "text", 457 "text-template", 458 "presentation", 459 "presentation-template", 460 }: 461 return self._formatted_text(rst_mode) 462 raise NotImplementedError(f"Type of document '{doc_type}' not supported yet")
Return content as text, with some formatting.
499 def get_formated_meta(self) -> str: 500 """Return meta informations as text, with some formatting.""" 501 result: list[str] = [] 502 503 # Simple values 504 def print_info(name: str, value: Any) -> None: 505 if value: 506 result.append(f"{name}: {value}") 507 508 meta = self.meta 509 print_info("Title", meta.get_title()) 510 print_info("Subject", meta.get_subject()) 511 print_info("Language", meta.get_language()) 512 print_info("Modification date", meta.get_modification_date()) 513 print_info("Creation date", meta.get_creation_date()) 514 print_info("Initial creator", meta.get_initial_creator()) 515 print_info("Keyword", meta.get_keywords()) 516 print_info("Editing duration", meta.get_editing_duration()) 517 print_info("Editing cycles", meta.get_editing_cycles()) 518 print_info("Generator", meta.get_generator()) 519 520 # Statistic 521 result.append("Statistic:") 522 statistic = meta.get_statistic() 523 if statistic: 524 for name, data in statistic.items(): 525 result.append(f" - {name[5:].replace('-', ' ').capitalize()}: {data}") 526 527 # User defined metadata 528 result.append("User defined metadata:") 529 user_metadata = meta.get_user_defined_metadata() 530 for name, data2 in user_metadata.items(): 531 result.append(f" - {name}: {data2}") 532 533 # And the description 534 print_info("Description", meta.get_description()) 535 536 return "\n".join(result) + "\n"
Return meta informations as text, with some formatting.
538 def add_file(self, path_or_file: str | Path) -> str: 539 """Insert a file from a path or a file-like object in the container. 540 541 Return the full path to reference in the content. 542 543 Arguments: 544 545 path_or_file -- str or Path or file-like 546 547 Return: str (URI) 548 """ 549 if not self.container: 550 raise ValueError("Empty Container") 551 name = "" 552 # Folder for added files (FIXME hard-coded and copied) 553 manifest = self.manifest 554 medias = manifest.get_paths() 555 # uuid = str(uuid4()) 556 557 if isinstance(path_or_file, (str, Path)): 558 path = Path(path_or_file) 559 extension = path.suffix.lower() 560 name = f"{path.stem}{extension}" 561 if posixpath.join("Pictures", name) in medias: 562 name = f"{path.stem}_{uuid4()}{extension}" 563 else: 564 path = None 565 name = getattr(path_or_file, "name", None) 566 if not name: 567 name = str(uuid4()) 568 media_type, _encoding = guess_type(name) 569 if not media_type: 570 media_type = "application/octet-stream" 571 if manifest.get_media_type("Pictures/") is None: 572 manifest.add_full_path("Pictures/") 573 full_path = posixpath.join("Pictures", name) 574 if path is None: 575 self.container.set_part(full_path, path_or_file.read()) # type:ignore 576 else: 577 self.container.set_part(full_path, path.read_bytes()) 578 manifest.add_full_path(full_path, media_type) 579 return full_path
Insert a file from a path or a file-like object in the container.
Return the full path to reference in the content.
Arguments:
path_or_file -- str or Path or file-like
Return: str (URI)
581 @property 582 def clone(self) -> Document: 583 """Return an exact copy of the document. 584 585 Return: Document 586 """ 587 clone = object.__new__(self.__class__) 588 for name in self.__dict__: 589 if name == "_Document__body": 590 setattr(clone, name, None) 591 elif name == "_Document__xmlparts": 592 setattr(clone, name, {}) 593 elif name == "container": 594 if not self.container: 595 raise ValueError("Empty Container") 596 setattr(clone, name, self.container.clone) 597 else: 598 value = deepcopy(getattr(self, name)) 599 setattr(clone, name, value) 600 return clone
Return an exact copy of the document.
Return: Document
602 def save( 603 self, 604 target: str | Path | io.BytesIO | None = None, 605 packaging: str = ZIP, 606 pretty: bool = False, 607 backup: bool = False, 608 ) -> None: 609 """Save the document, at the same place it was opened or at the given 610 target path. Target can also be a file-like object. It can be saved 611 as a Zip file (default), flat XML format or as files in a folder 612 (for debugging purpose). XML parts can be pretty printed. 613 614 Arguments: 615 616 target -- str or file-like object 617 618 packaging -- 'zip', 'folder', 'xml' 619 620 pretty -- bool 621 622 backup -- bool 623 """ 624 if not self.container: 625 raise ValueError("Empty Container") 626 # Some advertising 627 self.meta.set_generator_default() 628 # Synchronize data with container 629 container = self.container 630 for path, part in self.__xmlparts.items(): 631 if part is not None: 632 container.set_part(path, part.serialize(pretty)) 633 # Save the container 634 container.save(target, packaging=packaging, backup=backup)
Save the document, at the same place it was opened or at the given target path. Target can also be a file-like object. It can be saved as a Zip file (default), flat XML format or as files in a folder (for debugging purpose). XML parts can be pretty printed.
Arguments:
target -- str or file-like object
packaging -- 'zip', 'folder', 'xml'
pretty -- bool
backup -- bool
652 def get_styles( 653 self, 654 family: str | bytes = "", 655 automatic: bool = False, 656 ) -> list[Style | Element]: 657 # compatibility with old versions: 658 659 if isinstance(family, bytes): 660 family = bytes_to_str(family) 661 return self.content.get_styles(family=family) + self.styles.get_styles( 662 family=family, automatic=automatic 663 )
665 def get_style( 666 self, 667 family: str, 668 name_or_element: str | Style | None = None, 669 display_name: str | None = None, 670 ) -> Style | None: 671 """Return the style uniquely identified by the name/family pair. If 672 the argument is already a style object, it will return it. 673 674 If the name is None, the default style is fetched. 675 676 If the name is not the internal name but the name you gave in a 677 desktop application, use display_name instead. 678 679 Arguments: 680 681 family -- 'paragraph', 'text', 'graphic', 'table', 'list', 682 'number', 'page-layout', 'master-page' 683 684 name -- str or Element or None 685 686 display_name -- str 687 688 Return: Style or None if not found. 689 """ 690 # 1. content.xml 691 element = self.content.get_style( 692 family, name_or_element=name_or_element, display_name=display_name 693 ) 694 if element is not None: 695 return element 696 # 2. styles.xml 697 return self.styles.get_style( 698 family, 699 name_or_element=name_or_element, 700 display_name=display_name, 701 )
Return the style uniquely identified by the name/family pair. If the argument is already a style object, it will return it.
If the name is None, the default style is fetched.
If the name is not the internal name but the name you gave in a desktop application, use display_name instead.
Arguments:
family -- 'paragraph', 'text', 'graphic', 'table', 'list',
'number', 'page-layout', 'master-page'
name -- str or Element or None
display_name -- str
Return: Style or None if not found.
835 def insert_style( # noqa: C901 836 self, 837 style: Style | str, 838 name: str = "", 839 automatic: bool = False, 840 default: bool = False, 841 ) -> Any: 842 """Insert the given style object in the document, as required by the 843 style family and type. 844 845 The style is expected to be a common style with a name. In case it 846 was created with no name, the given can be set on the fly. 847 848 If automatic is True, the style will be inserted as an automatic 849 style. 850 851 If default is True, the style will be inserted as a default style and 852 would replace any existing default style of the same family. Any name 853 or display name would be ignored. 854 855 Automatic and default arguments are mutually exclusive. 856 857 All styles can't be used as default styles. Default styles are 858 allowed for the following families: paragraph, text, section, table, 859 table-column, table-row, table-cell, table-page, chart, drawing-page, 860 graphic, presentation, control and ruby. 861 862 Arguments: 863 864 style -- Style or str 865 866 name -- str 867 868 automatic -- bool 869 870 default -- bool 871 872 Return : style name -- str 873 """ 874 875 # if style is a str, assume it is the Style definition 876 if isinstance(style, str): 877 style_element: Style = Element.from_tag(style) # type: ignore 878 else: 879 style_element = style 880 if not isinstance(style_element, Element): 881 raise TypeError(f"Unknown Style type: '{style!r}'") 882 883 # Get family and name 884 family = self._pseudo_style_attribute(style_element, "family") 885 if not name: 886 name = self._pseudo_style_attribute(style_element, "name") 887 888 # Master page style 889 if family == "master-page": 890 existing, style_container = self._insert_style_get_master_page(family, name) 891 # Font face declarations 892 elif family == "font-face": 893 if default: 894 existing, style_container = self._insert_style_get_font_face_default( 895 family, name 896 ) 897 else: 898 existing, style_container = self._insert_style_get_font_face( 899 family, name 900 ) 901 # page layout style 902 elif family == "page-layout": 903 existing, style_container = self._insert_style_get_page_layout(family, name) 904 # Common style 905 elif family in FAMILY_ODF_STD or family in {"number"}: 906 existing, style_container = self._insert_style_standard( 907 style_element, name, family, automatic, default 908 ) 909 elif not family and style_element.__class__.__name__ == "DrawFillImage": 910 # special case for 'draw:fill-image' pseudo style 911 existing, style_container = self._insert_style_get_draw_fill_image(name) 912 # Invalid style 913 else: 914 raise ValueError( 915 "Invalid style: " 916 f"{style_element}, tag:{style_element.tag}, family:{family}" 917 ) 918 919 # Insert it! 920 if existing is not None: 921 style_container.delete(existing) 922 style_container.append(style_element) 923 return self._pseudo_style_attribute(style_element, "name")
Insert the given style object in the document, as required by the style family and type.
The style is expected to be a common style with a name. In case it was created with no name, the given can be set on the fly.
If automatic is True, the style will be inserted as an automatic style.
If default is True, the style will be inserted as a default style and would replace any existing default style of the same family. Any name or display name would be ignored.
Automatic and default arguments are mutually exclusive.
All styles can't be used as default styles. Default styles are allowed for the following families: paragraph, text, section, table, table-column, table-row, table-cell, table-page, chart, drawing-page, graphic, presentation, control and ruby.
Arguments:
style -- Style or str
name -- str
automatic -- bool
default -- bool
Return : style name -- str
925 def get_styled_elements(self, name: str = "") -> list[Element]: 926 """Brute-force to find paragraphs, tables, etc. using the given style 927 name (or all by default). 928 929 Arguments: 930 931 name -- str 932 933 Return: list 934 """ 935 # Header, footer, etc. have styles too 936 return self.content.root.get_styled_elements( 937 name 938 ) + self.styles.root.get_styled_elements(name)
Brute-force to find paragraphs, tables, etc. using the given style name (or all by default).
Arguments:
name -- str
Return: list
940 def show_styles( 941 self, 942 automatic: bool = True, 943 common: bool = True, 944 properties: bool = False, 945 ) -> str: 946 infos = [] 947 for style in self.get_styles(): 948 try: 949 name = style.name # type: ignore 950 except AttributeError: 951 print("--------------") 952 print(style.__class__) 953 print(style.serialize()) 954 raise 955 if style.__class__.__name__ == "DrawFillImage": 956 family = "" 957 else: 958 family = str(style.family) # type: ignore 959 parent = style.parent 960 is_auto = parent and parent.tag == "office:automatic-styles" 961 if is_auto and automatic is False or not is_auto and common is False: 962 continue 963 is_used = bool(self.get_styled_elements(name)) 964 infos.append( 965 { 966 "type": "auto " if is_auto else "common", 967 "used": "y" if is_used else "n", 968 "family": family, 969 "parent": self._pseudo_style_attribute(style, "parent_style") or "", 970 "name": name or "", 971 "display_name": self._pseudo_style_attribute(style, "display_name") 972 or "", 973 "properties": style.get_properties() if properties else None, # type: ignore 974 } 975 ) 976 if not infos: 977 return "" 978 # Sort by family and name 979 infos.sort(key=itemgetter("family", "name")) 980 # Show common and used first 981 infos.sort(key=itemgetter("type", "used"), reverse=True) 982 max_family = str(max([len(x["family"]) for x in infos])) # type: ignore 983 max_parent = str(max([len(x["parent"]) for x in infos])) # type: ignore 984 formater = ( 985 "%(type)s used:%(used)s family:%(family)-0" 986 + max_family 987 + "s parent:%(parent)-0" 988 + max_parent 989 + "s name:%(name)s" 990 ) 991 output = [] 992 for info in infos: 993 line = formater % info 994 if info["display_name"]: 995 line += " display_name:" + info["display_name"] # type: ignore 996 output.append(line) 997 if info["properties"]: 998 for name, value in info["properties"].items(): # type: ignore 999 output.append(f" - {name}: {value}") 1000 output.append("") 1001 return "\n".join(output)
1003 def delete_styles(self) -> int: 1004 """Remove all style information from content and all styles. 1005 1006 Return: number of deleted styles 1007 """ 1008 # First remove references to styles 1009 for element in self.get_styled_elements(): 1010 for attribute in ( 1011 "text:style-name", 1012 "draw:style-name", 1013 "draw:text-style-name", 1014 "table:style-name", 1015 "style:page-layout-name", 1016 ): 1017 try: 1018 element.del_attribute(attribute) 1019 except KeyError: 1020 continue 1021 # Then remove supposedly orphaned styles 1022 deleted = 0 1023 for style in self.get_styles(): 1024 if style.name is None: # type: ignore 1025 # Don't delete default styles 1026 continue 1027 # elif type(style) is odf_master_page: 1028 # # Don't suppress header and footer, just styling was removed 1029 # continue 1030 style.delete() 1031 deleted += 1 1032 return deleted
Remove all style information from content and all styles.
Return: number of deleted styles
1034 def merge_styles_from(self, document: Document) -> None: 1035 """Copy all the styles of a document into ourself. 1036 1037 Styles with the same type and name will be replaced, so only unique 1038 styles will be preserved. 1039 """ 1040 manifest = self.manifest 1041 document_manifest = document.manifest 1042 for style in document.get_styles(): 1043 tagname = style.tag 1044 family = self._pseudo_style_attribute(style, "family") 1045 stylename = style.name # type: ignore 1046 container = style.parent 1047 container_name = container.tag # type: ignore 1048 partname = container.parent.tag # type: ignore 1049 # The destination part 1050 if partname == "office:document-styles": 1051 part: Content | Styles = self.styles 1052 elif partname == "office:document-content": 1053 part = self.content 1054 else: 1055 raise NotImplementedError(partname) 1056 # Implemented containers 1057 if container_name not in { 1058 "office:styles", 1059 "office:automatic-styles", 1060 "office:master-styles", 1061 "office:font-face-decls", 1062 }: 1063 raise NotImplementedError(container_name) 1064 dest = part.get_element(f"//{container_name}") 1065 # Implemented style types 1066 # if tagname not in registered_styles: 1067 # raise NotImplementedError(tagname) 1068 duplicate = part.get_style(family, stylename) 1069 if duplicate is not None: 1070 duplicate.delete() 1071 dest.append(style) 1072 # Copy images from the header/footer 1073 if tagname == "style:master-page": 1074 query = "descendant::draw:image" 1075 for image in style.get_elements(query): 1076 url = image.url # type: ignore 1077 part_url = document.get_part(url) 1078 # Manually add the part to keep the name 1079 self.set_part(url, part_url) # type: ignore 1080 media_type = document_manifest.get_media_type(url) 1081 manifest.add_full_path(url, media_type) # type: ignore 1082 # Copy images from the fill-image 1083 elif tagname == "draw:fill-image": 1084 url = style.url # type: ignore 1085 part_url = document.get_part(url) 1086 self.set_part(url, part_url) # type: ignore 1087 media_type = document_manifest.get_media_type(url) 1088 manifest.add_full_path(url, media_type) # type: ignore
Copy all the styles of a document into ourself.
Styles with the same type and name will be replaced, so only unique styles will be preserved.
1090 def add_page_break_style(self) -> None: 1091 """Ensure that the document contains the style required for a manual page break. 1092 1093 Then a manual page break can be added to the document with: 1094 from paragraph import PageBreak 1095 ... 1096 document.body.append(PageBreak()) 1097 1098 Note: this style uses the property 'fo:break-after', another 1099 possibility could be the property 'fo:break-before' 1100 """ 1101 if existing := self.get_style( # noqa: SIM102 1102 family="paragraph", 1103 name_or_element="odfdopagebreak", 1104 ): 1105 if properties := existing.get_properties(): # noqa: SIM102 1106 if properties["fo:break-after"] == "page": 1107 return 1108 style = ( 1109 '<style:style style:family="paragraph" style:parent-style-name="Standard" ' 1110 'style:name="odfdopagebreak">' 1111 '<style:paragraph-properties fo:break-after="page"/></style:style>' 1112 ) 1113 self.insert_style(style, automatic=False)
Ensure that the document contains the style required for a manual page break.
Then a manual page break can be added to the document with: from paragraph import PageBreak ... document.body.append(PageBreak())
Note: this style uses the property 'fo:break-after', another possibility could be the property 'fo:break-before'
1115 def get_style_properties( 1116 self, family: str, name: str, area: str | None = None 1117 ) -> dict[str, str] | None: 1118 """Return the properties of the required style as a dict.""" 1119 style = self.get_style(family, name) 1120 if style is None: 1121 return None 1122 return style.get_properties(area=area) # type: ignore
Return the properties of the required style as a dict.
1124 def get_cell_style_properties( # noqa: C901 1125 self, table: str | int, coord: tuple | list | str 1126 ) -> dict[str, str]: # type: ignore 1127 """Return the style properties of a table cell of a .ods document, 1128 from the cell style or from the row style.""" 1129 1130 def _get_table(table: int | str) -> Table | None: 1131 table_pos = 0 1132 table_name = None 1133 if isinstance(table, int): 1134 table_pos = table 1135 elif isinstance(table, str): 1136 table_name = table_name 1137 else: 1138 raise TypeError(f"Table parameter must be int or str: {table!r}") 1139 return self.body.get_table( 1140 position=table_pos, name=table_name # type: ignore 1141 ) 1142 1143 if not (sheet := _get_table(table)): 1144 return {} 1145 cell = sheet.get_cell(coord, clone=False) 1146 if cell.style: 1147 return ( 1148 self.get_style_properties("table-cell", cell.style, "table-cell") or {} 1149 ) 1150 try: 1151 row = sheet.get_row(cell.y, clone=False, create=False) # type: ignore 1152 if row.style: # noqa: SIM102 1153 if props := self.get_style_properties( 1154 "table-row", row.style, "table-cell" 1155 ): 1156 return props 1157 column = sheet.get_column(cell.x) # type: ignore 1158 style = column.get_default_cell_style() 1159 if style: # noqa: SIM102 1160 if props := self.get_style_properties( 1161 "table-cell", style, "table-cell" 1162 ): 1163 return props 1164 except ValueError: 1165 pass 1166 return {}
Return the style properties of a table cell of a .ods document, from the cell style or from the row style.
1168 def get_cell_background_color( 1169 self, 1170 table: str | int, 1171 coord: tuple | list | str, 1172 default: str = "#ffffff", 1173 ) -> str: 1174 """Return the background color of a table cell of a .ods document, 1175 from the cell style, or from the row or column. 1176 1177 If color is not defined, return default value..""" 1178 found = self.get_cell_style_properties(table, coord).get("fo:background-color") 1179 return found or default
Return the background color of a table cell of a .ods document, from the cell style, or from the row or column.
If color is not defined, return default value..
85class DrawFillImage(DrawImage): 86 _tag = "draw:fill-image" 87 _properties: tuple[PropDef, ...] = ( 88 PropDef("display_name", "draw:display-name"), 89 PropDef("name", "draw:name"), 90 PropDef("height", "svg:height"), 91 PropDef("width", "svg:width"), 92 ) 93 94 def __init__( 95 self, 96 name: str | None = None, 97 display_name: str | None = None, 98 height: str | None = None, 99 width: str | None = None, 100 **kwargs: Any, 101 ) -> None: 102 """The "draw:fill-image" element specifies a link to a bitmap 103 resource. Fill image are not available as automatic styles. 104 The "draw:fill-image" element is usable within the following element: 105 "office:styles" 106 107 Arguments: 108 109 name -- str 110 111 display_name -- str 112 113 height -- str 114 115 width -- str 116 """ 117 super().__init__(**kwargs) 118 if self._do_init: 119 self.name = name 120 self.display_name = display_name 121 self.height = height 122 self.width = width
The "draw:image" element represents an image. An image can be either:
- A link to an external resource or
- Embedded in the document (Not implemented in this version)
Warning: image elements must be stored in a frame "draw:frame", see Frame().
94 def __init__( 95 self, 96 name: str | None = None, 97 display_name: str | None = None, 98 height: str | None = None, 99 width: str | None = None, 100 **kwargs: Any, 101 ) -> None: 102 """The "draw:fill-image" element specifies a link to a bitmap 103 resource. Fill image are not available as automatic styles. 104 The "draw:fill-image" element is usable within the following element: 105 "office:styles" 106 107 Arguments: 108 109 name -- str 110 111 display_name -- str 112 113 height -- str 114 115 width -- str 116 """ 117 super().__init__(**kwargs) 118 if self._do_init: 119 self.name = name 120 self.display_name = display_name 121 self.height = height 122 self.width = width
The "draw:fill-image" element specifies a link to a bitmap resource. Fill image are not available as automatic styles. The "draw:fill-image" element is usable within the following element: "office:styles"
Arguments:
name -- str
display_name -- str
height -- str
width -- str
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
315class DrawGroup(Element, AnchorMix, ZMix, PosMix): 316 """The DrawGroup "draw:g" element represents a group of drawing shapes. 317 318 Warning: implementation is currently minimal. 319 320 Drawing shapes contained by a "draw:g" element that is itself 321 contained by a "draw:a" element, act as hyperlinks using the 322 xlink:href attribute of the containing "draw:a" element. If the 323 included drawing shapes are themselves contained within "draw:a" 324 elements, then the xlink:href attributes of those "draw:a" elements 325 act as the hyperlink information for the shapes they contain. 326 327 The "draw:g" element has the following attributes: draw:caption-id, 328 draw:class-names, draw:id, draw:name, draw:style-name, draw:z-index, 329 presentation:class-names, presentation:style-name, svg:y, 330 table:end-cell-address, table:end-x, table:end-y, 331 table:table-background, text:anchor-page-number, text:anchor-type, 332 and xml:id. 333 334 The "draw:g" element has the following child elements: "dr3d:scene", 335 "draw:a", "draw:caption", "draw:circle", "draw:connector", 336 "draw:control", "draw:custom-shape", "draw:ellipse", "draw:frame", 337 "draw:g", "draw:glue-point", "draw:line", "draw:measure", 338 "draw:page-thumbnail", "draw:path", "draw:polygon", "draw:polyline", 339 "draw:rect", "draw:regular-polygon", "office:event-listeners", 340 "svg:desc" and "svg:title". 341 """ 342 343 _tag = "draw:g" 344 _properties: tuple[PropDef, ...] = ( 345 PropDef("draw_id", "draw:id"), 346 PropDef("caption_id", "draw:caption-id"), 347 PropDef("draw_class_names", "draw:class-names"), 348 PropDef("name", "draw:name"), 349 PropDef("style", "draw:style-name"), 350 # ('z_index', 'draw:z-index'), 351 PropDef("presentation_class_names", "presentation:class-names"), 352 PropDef("presentation_style", "presentation:style-name"), 353 PropDef("table_end_cell", "table:end-cell-address"), 354 PropDef("table_end_x", "table:end-x"), 355 PropDef("table_end_y", "table:end-y"), 356 PropDef("table_background", "table:table-background"), 357 # ('anchor_page', 'text:anchor-page-number'), 358 # ('anchor_type', 'text:anchor-type'), 359 PropDef("xml_id", "xml:id"), 360 PropDef("pos_x", "svg:x"), 361 PropDef("pos_y", "svg:y"), 362 ) 363 364 def __init__( 365 self, 366 name: str | None = None, 367 draw_id: str | None = None, 368 style: str | None = None, 369 position: tuple | None = None, 370 z_index: int = 0, 371 anchor_type: str | None = None, 372 anchor_page: int | None = None, 373 presentation_style: str | None = None, 374 **kwargs: Any, 375 ) -> None: 376 super().__init__(**kwargs) 377 if self._do_init: 378 if z_index is not None: 379 self.z_index = z_index 380 if name: 381 self.name = name 382 if draw_id is not None: 383 self.draw_id = draw_id 384 if style is not None: 385 self.style = style 386 if position is not None: 387 self.position = position 388 if anchor_type: 389 self.anchor_type = anchor_type 390 if anchor_page is not None: 391 self.anchor_page = anchor_page 392 if presentation_style is not None: 393 self.presentation_style = presentation_style
The DrawGroup "draw:g" element represents a group of drawing shapes.
Warning: implementation is currently minimal.
Drawing shapes contained by a "draw:g" element that is itself contained by a "draw:a" element, act as hyperlinks using the xlink:href attribute of the containing "draw:a" element. If the included drawing shapes are themselves contained within "draw:a" elements, then the xlink:href attributes of those "draw:a" elements act as the hyperlink information for the shapes they contain.
The "draw:g" element has the following attributes: draw:caption-id, draw:class-names, draw:id, draw:name, draw:style-name, draw:z-index, presentation:class-names, presentation:style-name, svg:y, table:end-cell-address, table:end-x, table:end-y, table:table-background, text:anchor-page-number, text:anchor-type, and xml:id.
The "draw:g" element has the following child elements: "dr3d:scene", "draw:a", "draw:caption", "draw:circle", "draw:connector", "draw:control", "draw:custom-shape", "draw:ellipse", "draw:frame", "draw:g", "draw:glue-point", "draw:line", "draw:measure", "draw:page-thumbnail", "draw:path", "draw:polygon", "draw:polyline", "draw:rect", "draw:regular-polygon", "office:event-listeners", "svg:desc" and "svg:title".
364 def __init__( 365 self, 366 name: str | None = None, 367 draw_id: str | None = None, 368 style: str | None = None, 369 position: tuple | None = None, 370 z_index: int = 0, 371 anchor_type: str | None = None, 372 anchor_page: int | None = None, 373 presentation_style: str | None = None, 374 **kwargs: Any, 375 ) -> None: 376 super().__init__(**kwargs) 377 if self._do_init: 378 if z_index is not None: 379 self.z_index = z_index 380 if name: 381 self.name = name 382 if draw_id is not None: 383 self.draw_id = draw_id 384 if style is not None: 385 self.style = style 386 if position is not None: 387 self.position = position 388 if anchor_type: 389 self.anchor_type = anchor_type 390 if anchor_page is not None: 391 self.anchor_page = anchor_page 392 if presentation_style is not None: 393 self.presentation_style = presentation_style
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
- odfdo.frame.AnchorMix
- ANCHOR_VALUE_CHOICE
- anchor_type
- anchor_page
- odfdo.frame.ZMix
- z_index
- odfdo.frame.PosMix
- position
31class DrawImage(Element): 32 """The "draw:image" element represents an image. An image can be 33 either: 34 - A link to an external resource or 35 - Embedded in the document (Not implemented in this version) 36 37 Warning: image elements must be stored in a frame "draw:frame", 38 see Frame(). 39 """ 40 41 _tag = "draw:image" 42 _properties: tuple[PropDef, ...] = ( 43 PropDef("url", "xlink:href"), 44 PropDef("type", "xlink:type"), 45 PropDef("show", "xlink:show"), 46 PropDef("actuate", "xlink:actuate"), 47 PropDef("filter_name", "draw:filter-name"), 48 ) 49 50 def __init__( 51 self, 52 url: str = "", 53 xlink_type: str = "simple", 54 show: str = "embed", 55 actuate: str = "onLoad", 56 filter_name: str | None = None, 57 **kwargs: Any, 58 ) -> None: 59 """Initialisation of an DrawImage. 60 61 Arguments: 62 63 url -- str 64 65 type -- str 66 67 show -- str 68 69 actuate -- str 70 71 filter_name -- str 72 """ 73 super().__init__(**kwargs) 74 if self._do_init: 75 self.url = url 76 self.type = xlink_type 77 self.show = show 78 self.actuate = actuate 79 self.filter_name = filter_name
The "draw:image" element represents an image. An image can be either:
- A link to an external resource or
- Embedded in the document (Not implemented in this version)
Warning: image elements must be stored in a frame "draw:frame", see Frame().
50 def __init__( 51 self, 52 url: str = "", 53 xlink_type: str = "simple", 54 show: str = "embed", 55 actuate: str = "onLoad", 56 filter_name: str | None = None, 57 **kwargs: Any, 58 ) -> None: 59 """Initialisation of an DrawImage. 60 61 Arguments: 62 63 url -- str 64 65 type -- str 66 67 show -- str 68 69 actuate -- str 70 71 filter_name -- str 72 """ 73 super().__init__(**kwargs) 74 if self._do_init: 75 self.url = url 76 self.type = xlink_type 77 self.show = show 78 self.actuate = actuate 79 self.filter_name = filter_name
Initialisation of an DrawImage.
Arguments:
url -- str
type -- str
show -- str
actuate -- str
filter_name -- str
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
33class DrawPage(Element): 34 """ODF draw page "draw:page", for pages of presentation and drawings.""" 35 36 _tag = "draw:page" 37 _properties = ( 38 PropDef("name", "draw:name"), 39 PropDef("draw_id", "draw:id"), 40 PropDef("master_page", "draw:master-page-name"), 41 PropDef( 42 "presentation_page_layout", "presentation:presentation-page-layout-name" 43 ), 44 PropDef("style", "draw:style-name"), 45 ) 46 47 def __init__( 48 self, 49 draw_id: str | None = None, 50 name: str | None = None, 51 master_page: str | None = None, 52 presentation_page_layout: str | None = None, 53 style: str | None = None, 54 **kwargs: Any, 55 ) -> None: 56 """ 57 Arguments: 58 59 draw_id -- str 60 61 name -- str 62 63 master_page -- str 64 65 presentation_page_layout -- str 66 67 style -- str 68 """ 69 super().__init__(**kwargs) 70 if self._do_init: 71 if draw_id: 72 self.draw_id = draw_id 73 if name: 74 self.name = name 75 if master_page: 76 self.master_page = master_page 77 if presentation_page_layout: 78 self.presentation_page_layout = presentation_page_layout 79 if style: 80 self.style = style 81 82 def set_transition( 83 self, 84 smil_type: str, 85 subtype: str | None = None, 86 dur: str = "2s", 87 ) -> None: 88 # Create the new animation 89 anim_page = AnimPar(presentation_node_type="timing-root") 90 anim_begin = AnimPar(smil_begin=f"{self.draw_id}.begin") 91 transition = AnimTransFilter( 92 smil_dur=dur, smil_type=smil_type, smil_subtype=subtype 93 ) 94 anim_page.append(anim_begin) 95 anim_begin.append(transition) 96 97 # Replace when already a transition: 98 # anim:seq => After the frame's transition 99 # cf page 349 of OpenDocument-v1.0-os.pdf 100 # Conclusion: We must delete the first child 'anim:par' 101 existing = self.get_element("anim:par") 102 if existing: 103 self.delete(existing) 104 self.append(anim_page) 105 106 def get_shapes(self) -> list[Element]: 107 query = "(descendant::" + "|descendant::".join(registered_shapes) + ")" 108 return self.get_elements(query) 109 110 def get_formatted_text(self, context: dict | None = None) -> str: 111 result: list[str] = [] 112 for child in self.children: 113 if child.tag == "presentation:notes": 114 # No need for an advanced odf_notes.get_formatted_text() 115 # because the text seems to be only contained in paragraphs 116 # and frames, that we already handle 117 for sub_child in child.children: 118 result.append(sub_child.get_formatted_text(context)) 119 result.append("\n") 120 result.append(child.get_formatted_text(context)) 121 result.append("\n") 122 return "".join(result)
ODF draw page "draw:page", for pages of presentation and drawings.
47 def __init__( 48 self, 49 draw_id: str | None = None, 50 name: str | None = None, 51 master_page: str | None = None, 52 presentation_page_layout: str | None = None, 53 style: str | None = None, 54 **kwargs: Any, 55 ) -> None: 56 """ 57 Arguments: 58 59 draw_id -- str 60 61 name -- str 62 63 master_page -- str 64 65 presentation_page_layout -- str 66 67 style -- str 68 """ 69 super().__init__(**kwargs) 70 if self._do_init: 71 if draw_id: 72 self.draw_id = draw_id 73 if name: 74 self.name = name 75 if master_page: 76 self.master_page = master_page 77 if presentation_page_layout: 78 self.presentation_page_layout = presentation_page_layout 79 if style: 80 self.style = style
Arguments:
draw_id -- str
name -- str
master_page -- str
presentation_page_layout -- str
style -- str
82 def set_transition( 83 self, 84 smil_type: str, 85 subtype: str | None = None, 86 dur: str = "2s", 87 ) -> None: 88 # Create the new animation 89 anim_page = AnimPar(presentation_node_type="timing-root") 90 anim_begin = AnimPar(smil_begin=f"{self.draw_id}.begin") 91 transition = AnimTransFilter( 92 smil_dur=dur, smil_type=smil_type, smil_subtype=subtype 93 ) 94 anim_page.append(anim_begin) 95 anim_begin.append(transition) 96 97 # Replace when already a transition: 98 # anim:seq => After the frame's transition 99 # cf page 349 of OpenDocument-v1.0-os.pdf 100 # Conclusion: We must delete the first child 'anim:par' 101 existing = self.get_element("anim:par") 102 if existing: 103 self.delete(existing) 104 self.append(anim_page)
110 def get_formatted_text(self, context: dict | None = None) -> str: 111 result: list[str] = [] 112 for child in self.children: 113 if child.tag == "presentation:notes": 114 # No need for an advanced odf_notes.get_formatted_text() 115 # because the text seems to be only contained in paragraphs 116 # and frames, that we already handle 117 for sub_child in child.children: 118 result.append(sub_child.get_formatted_text(context)) 119 result.append("\n") 120 result.append(child.get_formatted_text(context)) 121 result.append("\n") 122 return "".join(result)
This function should return a beautiful version of the text.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
306class Element(CachedElement): 307 """Super class of all ODF classes. 308 309 Representation of an XML element. Abstraction of the XML library behind. 310 """ 311 312 _tag: str = "" 313 _caching: bool = False 314 _properties: tuple[PropDef, ...] = () 315 316 def __init__(self, **kwargs: Any) -> None: 317 tag_or_elem = kwargs.pop("tag_or_elem", None) 318 if tag_or_elem is None: 319 # Instance for newly created object: create new lxml element and 320 # continue by subclass __init__ 321 # If the tag key word exists, make a custom element 322 self._do_init = True 323 tag = kwargs.pop("tag", self._tag) 324 self.__element = self.make_etree_element(tag) 325 else: 326 # called with an existing lxml element, sould be a result of 327 # from_tag() casting, do not execute the subclass __init__ 328 if not isinstance(tag_or_elem, _Element): 329 raise TypeError(f'"{type(tag_or_elem)}" is not an element node') 330 self._do_init = False 331 self.__element = tag_or_elem 332 333 def __repr__(self) -> str: 334 return f"<{self.__class__.__name__} tag={self.tag}>" 335 336 def __str__(self) -> str: 337 return self.text_recursive 338 339 @classmethod 340 def from_tag(cls, tag_or_elem: str | _Element) -> Element: 341 """Element class and subclass factory. 342 343 Turn an lxml Element or ODF string tag into an ODF XML Element 344 of the relevant class. 345 346 Arguments: 347 348 tag_or_elem -- ODF str tag or lxml.Element 349 350 Return: Element (or subclass) instance 351 """ 352 if isinstance(tag_or_elem, str): 353 # assume the argument is a prefix:name tag 354 elem = cls.make_etree_element(tag_or_elem) 355 else: 356 elem = tag_or_elem 357 klass = _class_registry.get(elem.tag, cls) 358 return klass(tag_or_elem=elem) 359 360 @classmethod 361 def from_tag_for_clone( 362 cls: type, 363 tree_element: _Element, 364 cache: tuple | None, 365 ) -> Element: 366 tag = to_str(tree_element.tag) 367 klass = _class_registry.get(tag, cls) 368 element = klass(tag_or_elem=tree_element) 369 if cache and element._caching: 370 element._tmap = cache[0] 371 element._cmap = cache[1] 372 if len(cache) == 3: 373 element._rmap = cache[2] 374 return element 375 376 @staticmethod 377 def make_etree_element(tag: str) -> _Element: 378 if not isinstance(tag, str): 379 raise TypeError(f"Tag is not str: {tag!r}") 380 tag = tag.strip() 381 if not tag: 382 raise ValueError("Tag is empty") 383 if "<" not in tag: 384 # Qualified name 385 # XXX don't build the element from scratch or lxml will pollute with 386 # repeated namespace declarations 387 tag = f"<{tag}/>" 388 # XML fragment 389 root = fromstring(NAMESPACES_XML % str_to_bytes(tag)) 390 return root[0] 391 392 @staticmethod 393 def _generic_attrib_getter(attr_name: str, family: str | None = None) -> Callable: 394 name = _get_lxml_tag(attr_name) 395 396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value) 408 409 return getter 410 411 @staticmethod 412 def _generic_attrib_setter(attr_name: str, family: str | None = None) -> Callable: 413 name = _get_lxml_tag(attr_name) 414 415 def setter(self: Element, value: Any) -> None: 416 try: 417 if family and self.family != family: # type: ignore 418 return None 419 except AttributeError: 420 return None 421 if value is None: 422 with contextlib.suppress(KeyError): 423 del self.__element.attrib[name] 424 return 425 if isinstance(value, bool): 426 value = Boolean.encode(value) 427 self.__element.set(name, str(value)) 428 429 return setter 430 431 @classmethod 432 def _define_attribut_property(cls: type[Element]) -> None: 433 for prop in cls._properties: 434 setattr( 435 cls, 436 prop.name, 437 property( 438 cls._generic_attrib_getter(prop.attr, prop.family or None), 439 cls._generic_attrib_setter(prop.attr, prop.family or None), 440 None, 441 f"Get/set the attribute {prop.attr}", 442 ), 443 ) 444 445 @staticmethod 446 def _make_before_regex( 447 before: str | None, 448 after: str | None, 449 ) -> re.Pattern: 450 # 1) before xor after is not None 451 if before is not None: 452 return re.compile(before) 453 else: 454 if after is None: 455 raise ValueError("Both 'before' and 'after' are None") 456 return re.compile(after) 457 458 @staticmethod 459 def _search_negative_position( 460 xpath_result: list, 461 regex: re.Pattern, 462 ) -> tuple[str, re.Match]: 463 # Found the last text that matches the regex 464 text = None 465 for a_text in xpath_result: 466 if regex.search(str(a_text)) is not None: 467 text = a_text 468 if text is None: 469 raise ValueError(f"Text not found: '{xpath_result}'") 470 if not isinstance(text, str): 471 raise TypeError(f"Text not found or text not of type str: '{text}'") 472 return text, list(regex.finditer(text))[-1] 473 474 @staticmethod 475 def _search_positive_position( 476 xpath_result: list, 477 regex: re.Pattern, 478 position: int, 479 ) -> tuple[str, re.Match]: 480 # Found the last text that matches the regex 481 count = 0 482 for text in xpath_result: 483 found_nb = len(regex.findall(str(text))) 484 if found_nb + count >= position + 1: 485 break 486 count += found_nb 487 else: 488 raise ValueError(f"Text not found: '{xpath_result}'") 489 if not isinstance(text, str): 490 raise TypeError(f"Text not found or text not of type str: '{text}'") 491 return text, list(regex.finditer(text))[position - count] 492 493 def _insert_before_after( 494 self, 495 current: _Element, 496 element: _Element, 497 before: str | None, 498 after: str | None, 499 position: int, 500 xpath_text: XPath, 501 ) -> tuple[int, str]: 502 regex = self._make_before_regex(before, after) 503 xpath_result = xpath_text(current) 504 if not isinstance(xpath_result, list): 505 raise TypeError("Bad XPath result") 506 # position = -1 507 if position < 0: 508 text, sre = self._search_negative_position(xpath_result, regex) 509 # position >= 0 510 else: 511 text, sre = self._search_positive_position(xpath_result, regex, position) 512 # Compute pos 513 if before is None: 514 pos = sre.end() 515 else: 516 pos = sre.start() 517 return pos, text 518 519 def _insert_find_text( 520 self, 521 current: _Element, 522 element: _Element, 523 before: str | None, 524 after: str | None, 525 position: int, 526 xpath_text: XPath, 527 ) -> tuple[int, str]: 528 # Find the text 529 xpath_result = xpath_text(current) 530 if not isinstance(xpath_result, list): 531 raise TypeError("Bad XPath result") 532 count = 0 533 for text in xpath_result: 534 if not isinstance(text, str): 535 continue 536 found_nb = len(text) 537 if found_nb + count >= position: 538 break 539 count += found_nb 540 else: 541 raise ValueError("Text not found") 542 # We insert before the character 543 pos = position - count 544 return pos, text 545 546 def _insert( 547 self, 548 element: Element, 549 before: str | None = None, 550 after: str | None = None, 551 position: int = 0, 552 main_text: bool = False, 553 ) -> None: 554 """Insert an element before or after the characters in the text which 555 match the regex before/after. 556 557 When the regex matches more of one part of the text, position can be 558 set to choice which part must be used. If before and after are None, 559 we use only position that is the number of characters. If position is 560 positive and before=after=None, we insert before the position 561 character. But if position=-1, we insert after the last character. 562 563 564 Arguments: 565 566 element -- Element 567 568 before -- str regex 569 570 after -- str regex 571 572 position -- int 573 """ 574 # not implemented: if main_text is True, filter out the annotations texts in computation. 575 current = self.__element 576 xelement = element.__element 577 578 if main_text: 579 xpath_text = _xpath_text_main_descendant 580 else: 581 xpath_text = _xpath_text_descendant 582 583 # 1) before xor after is not None 584 if (before is not None) ^ (after is not None): 585 pos, text = self._insert_before_after( 586 current, 587 xelement, 588 before, 589 after, 590 position, 591 xpath_text, 592 ) 593 # 2) before=after=None => only with position 594 elif before is None and after is None: 595 # Hack if position is negative => quickly 596 if position < 0: 597 current.append(xelement) 598 return 599 pos, text = self._insert_find_text( 600 current, 601 xelement, 602 before, 603 after, 604 position, 605 xpath_text, 606 ) 607 else: 608 raise ValueError("bad combination of arguments") 609 610 # Compute new texts 611 text_before = text[:pos] if text[:pos] else None 612 text_after = text[pos:] if text[pos:] else None 613 614 # Insert! 615 parent = text.getparent() # type: ignore 616 if text.is_text: # type: ignore 617 parent.text = text_before 618 element.tail = text_after 619 parent.insert(0, xelement) 620 else: 621 parent.addnext(xelement) 622 parent.tail = text_before 623 element.tail = text_after 624 625 def _insert_between( # noqa: C901 626 self, 627 element: Element, 628 from_: str, 629 to: str, 630 ) -> None: 631 """Insert the given empty element to wrap the text beginning with 632 "from_" and ending with "to". 633 634 Example 1: '<p>toto tata titi</p> 635 636 We want to insert a link around "tata". 637 638 Result 1: '<p>toto <a>tata</a> titi</p> 639 640 Example 2: '<p><span>toto</span> tata titi</p> 641 642 We want to insert a link around "tata". 643 644 Result 2: '<p><span>toto</span> <a>tata</a> titi</p> 645 646 Example 3: '<p>toto <span> tata </span> titi</p>' 647 648 We want to insert a link from "tata" to "titi" included. 649 650 Result 3: '<p>toto <span> </span>' 651 '<a><span>tata </span> titi</a></p>' 652 653 Example 4: '<p>toto <span>tata titi</span> tutu</p>' 654 655 We want to insert a link from "titi" to "tutu" 656 657 Result 4: '<p>toto <span>tata </span><a><span>titi</span></a>' 658 '<a> tutu</a></p>' 659 660 Example 5: '<p>toto <span>tata titi</span> ' 661 '<span>tutu tyty</span></p>' 662 663 We want to insert a link from "titi" to "tutu" 664 665 Result 5: '<p>toto <span>tata </span><a><span>titi</span><a> ' 666 '<a> <span>tutu</span></a><span> tyty</span></p>' 667 """ 668 current = self.__element 669 wrapper = element.__element 670 671 xpath_result = _xpath_text_descendant(current) 672 if not isinstance(xpath_result, list): 673 raise TypeError("Bad XPath result") 674 675 for text in xpath_result: 676 if not isinstance(text, str): 677 raise TypeError("Text not found or text not of type str") 678 if from_ not in text: 679 continue 680 from_index = text.index(from_) 681 text_before = text[:from_index] 682 text_after = text[from_index:] 683 from_container = text.getparent() # type: ignore 684 if not isinstance(from_container, _Element): 685 raise TypeError("Bad XPath result") 686 # Include from_index to match a single word 687 to_index = text.find(to, from_index) 688 if to_index >= 0: 689 # Simple case: "from" and "to" in the same element 690 to_end = to_index + len(to) 691 if text.is_text: # type: ignore 692 from_container.text = text_before 693 wrapper.text = text[to_index:to_end] 694 wrapper.tail = text[to_end:] 695 from_container.insert(0, wrapper) 696 else: 697 from_container.tail = text_before 698 wrapper.text = text[to_index:to_end] 699 wrapper.tail = text[to_end:] 700 parent = from_container.getparent() 701 index = parent.index(from_container) # type: ignore 702 parent.insert(index + 1, wrapper) # type: ignore 703 return 704 else: 705 # Exit to the second part where we search for the end text 706 break 707 else: 708 raise ValueError("Start text not found") 709 710 # The container is split in two 711 container2 = deepcopy(from_container) 712 if text.is_text: # type: ignore 713 from_container.text = text_before 714 from_container.tail = None 715 container2.text = text_after 716 from_container.tail = None 717 else: 718 from_container.tail = text_before 719 container2.tail = text_after 720 # Stack the copy into the surrounding element 721 wrapper.append(container2) 722 parent = from_container.getparent() 723 index = parent.index(from_container) # type: ignore 724 parent.insert(index + 1, wrapper) # type: ignore 725 726 xpath_result = _xpath_text_descendant(wrapper) 727 if not isinstance(xpath_result, list): 728 raise TypeError("Bad XPath result") 729 730 for text in xpath_result: 731 if not isinstance(text, str): 732 raise TypeError("Text not found or text not of type str") 733 if to not in text: 734 continue 735 to_end = text.index(to) + len(to) 736 text_before = text[:to_end] 737 text_after = text[to_end:] 738 container_to = text.getparent() # type: ignore 739 if not isinstance(container_to, _Element): 740 raise TypeError("Bad XPath result") 741 if text.is_text: # type: ignore 742 container_to.text = text_before 743 container_to.tail = text_after 744 else: 745 container_to.tail = text_before 746 next_one = container_to.getnext() 747 if next_one is None: 748 next_one = container_to.getparent() 749 next_one.tail = text_after # type: ignore 750 return 751 raise ValueError("End text not found") 752 753 @property 754 def tag(self) -> str: 755 """Get/set the underlying xml tag with the given qualified name. 756 757 Warning: direct change of tag does not change the element class. 758 759 Arguments: 760 761 qname -- str (e.g. "text:span") 762 """ 763 return _get_prefixed_name(self.__element.tag) 764 765 @tag.setter 766 def tag(self, qname: str) -> None: 767 self.__element.tag = _get_lxml_tag(qname) 768 769 def elements_repeated_sequence( 770 self, 771 xpath_instance: XPath, 772 name: str, 773 ) -> list[tuple[int, int]]: 774 """Utility method for table module.""" 775 lxml_tag = _get_lxml_tag_or_name(name) 776 element = self.__element 777 sub_elements = xpath_instance(element) 778 if not isinstance(sub_elements, list): 779 raise TypeError("Bad XPath result.") 780 result: list[tuple[int, int]] = [] 781 idx = -1 782 for sub_element in sub_elements: 783 if not isinstance(sub_element, _Element): 784 continue 785 idx += 1 786 value = sub_element.get(lxml_tag) 787 if value is None: 788 result.append((idx, 1)) 789 continue 790 try: 791 int_value = int(value) 792 except ValueError: 793 int_value = 1 794 result.append((idx, max(int_value, 1))) 795 return result 796 797 def get_elements(self, xpath_query: XPath | str) -> list[Element]: 798 cache: tuple | None = None 799 element = self.__element 800 if isinstance(xpath_query, str): 801 new_xpath_query = xpath_compile(xpath_query) 802 result = new_xpath_query(element) 803 else: 804 result = xpath_query(element) 805 if not isinstance(result, list): 806 raise TypeError("Bad XPath result") 807 808 if hasattr(self, "_tmap"): 809 if hasattr(self, "_rmap"): 810 cache = (self._tmap, self._cmap, self._rmap) 811 else: 812 cache = (self._tmap, self._cmap) 813 return [ 814 Element.from_tag_for_clone(e, cache) 815 for e in result 816 if isinstance(e, _Element) 817 ] 818 819 # fixme : need original get_element as wrapper of get_elements 820 821 def get_element(self, xpath_query: XPath | str) -> Element | None: 822 element = self.__element 823 result = element.xpath(f"({xpath_query})[1]", namespaces=ODF_NAMESPACES) 824 if result: 825 return Element.from_tag(result[0]) # type:ignore 826 return None 827 828 def _get_element_idx(self, xpath_query: XPath | str, idx: int) -> Element | None: 829 element = self.__element 830 result = element.xpath(f"({xpath_query})[{idx + 1}]", namespaces=ODF_NAMESPACES) 831 if result: 832 return Element.from_tag(result[0]) # type:ignore 833 return None 834 835 def _get_element_idx2(self, xpath_instance: XPath, idx: int) -> Element | None: 836 element = self.__element 837 result = xpath_instance(element, idx=idx + 1) 838 if result: 839 return Element.from_tag(result[0]) # type:ignore 840 return None 841 842 @property 843 def attributes(self) -> dict[str, str]: 844 return { 845 _get_prefixed_name(str(key)): str(value) 846 for key, value in self.__element.attrib.items() 847 } 848 849 def get_attribute(self, name: str) -> str | bool | None: 850 """Return the attribute value as type str | bool | None.""" 851 element = self.__element 852 lxml_tag = _get_lxml_tag_or_name(name) 853 value = element.get(lxml_tag) 854 if value is None: 855 return None 856 elif value in ("true", "false"): 857 return Boolean.decode(value) 858 return str(value) 859 860 def get_attribute_integer(self, name: str) -> int | None: 861 """Return either the attribute as type int, or None.""" 862 element = self.__element 863 lxml_tag = _get_lxml_tag_or_name(name) 864 value = element.get(lxml_tag) 865 if value is None: 866 return None 867 try: 868 return int(value) 869 except ValueError: 870 return None 871 872 def get_attribute_string(self, name: str) -> str | None: 873 """Return either the attribute as type str, or None.""" 874 element = self.__element 875 lxml_tag = _get_lxml_tag_or_name(name) 876 value = element.get(lxml_tag) 877 if value is None: 878 return None 879 return str(value) 880 881 def set_attribute( 882 self, name: str, value: bool | str | tuple[int, int, int] | None 883 ) -> None: 884 if name in ODF_COLOR_PROPERTY: 885 if isinstance(value, bool): 886 raise TypeError(f"Wrong color type {value!r}") 887 if value != "transparent": 888 value = hexa_color(value) 889 element = self.__element 890 lxml_tag = _get_lxml_tag_or_name(name) 891 if isinstance(value, bool): 892 value = Boolean.encode(value) 893 elif value is None: 894 with contextlib.suppress(KeyError): 895 del element.attrib[lxml_tag] 896 return 897 element.set(lxml_tag, str(value)) 898 899 def set_style_attribute(self, name: str, value: Element | str) -> None: 900 """Shortcut to accept a style object as a value.""" 901 if isinstance(value, Element): 902 value = str(value.name) # type:ignore 903 return self.set_attribute(name, value) 904 905 def del_attribute(self, name: str) -> None: 906 element = self.__element 907 lxml_tag = _get_lxml_tag_or_name(name) 908 del element.attrib[lxml_tag] 909 910 @property 911 def text(self) -> str: 912 """Get / set the text content of the element.""" 913 return self.__element.text or "" 914 915 @text.setter 916 def text(self, text: str | None) -> None: 917 if text is None: 918 text = "" 919 try: 920 self.__element.text = text 921 except TypeError as e: 922 raise TypeError(f'Str type expected: "{type(text)}"') from e 923 924 @property 925 def text_recursive(self) -> str: 926 return "".join(str(x) for x in self.__element.itertext()) 927 928 @property 929 def tail(self) -> str | None: 930 """Get / set the text immediately following the element.""" 931 return self.__element.tail 932 933 @tail.setter 934 def tail(self, text: str | None) -> None: 935 self.__element.tail = text or "" 936 937 def search(self, pattern: str) -> int | None: 938 """Return the first position of the pattern in the text content of 939 the element, or None if not found. 940 941 Python regular expression syntax applies. 942 943 Arguments: 944 945 pattern -- str 946 947 Return: int or None 948 """ 949 match = re.search(pattern, self.text_recursive) 950 if match is None: 951 return None 952 return match.start() 953 954 def match(self, pattern: str) -> bool: 955 """return True if the pattern is found one or more times anywhere in 956 the text content of the element. 957 958 Python regular expression syntax applies. 959 960 Arguments: 961 962 pattern -- str 963 964 Return: bool 965 """ 966 return self.search(pattern) is not None 967 968 def replace(self, pattern: str, new: str | None = None) -> int: 969 """Replace the pattern with the given text, or delete if text is an 970 empty string, and return the number of replacements. By default, only 971 return the number of occurences that would be replaced. 972 973 It cannot replace patterns found across several element, like a word 974 split into two consecutive spans. 975 976 Python regular expression syntax applies. 977 978 Arguments: 979 980 pattern -- str 981 982 new -- str 983 984 Return: int 985 """ 986 if not isinstance(pattern, str): 987 # Fail properly if the pattern is an non-ascii bytestring 988 pattern = str(pattern) 989 cpattern = re.compile(pattern) 990 count = 0 991 for text in self.xpath("descendant::text()"): 992 if new is None: 993 count += len(cpattern.findall(str(text))) 994 else: 995 new_text, number = cpattern.subn(new, str(text)) 996 container = text.parent 997 if text.is_text(): # type: ignore 998 container.text = new_text # type: ignore 999 else: 1000 container.tail = new_text # type: ignore 1001 count += number 1002 return count 1003 1004 @property 1005 def root(self) -> Element: 1006 element = self.__element 1007 tree = element.getroottree() 1008 root = tree.getroot() 1009 return Element.from_tag(root) 1010 1011 @property 1012 def parent(self) -> Element | None: 1013 element = self.__element 1014 parent = element.getparent() 1015 if parent is None: 1016 # Already at root 1017 return None 1018 return Element.from_tag(parent) 1019 1020 @property 1021 def is_bound(self) -> bool: 1022 return self.parent is not None 1023 1024 # def get_next_sibling(self): 1025 # element = self.__element 1026 # next_one = element.getnext() 1027 # if next_one is None: 1028 # return None 1029 # return Element.from_tag(next_one) 1030 # 1031 # def get_prev_sibling(self): 1032 # element = self.__element 1033 # prev = element.getprevious() 1034 # if prev is None: 1035 # return None 1036 # return Element.from_tag(prev) 1037 1038 @property 1039 def children(self) -> list[Element]: 1040 element = self.__element 1041 return [ 1042 Element.from_tag(e) 1043 for e in element.iterchildren() 1044 if isinstance(e, _Element) 1045 ] 1046 1047 def index(self, child: Element) -> int: 1048 """Return the position of the child in this element. 1049 1050 Inspired by lxml 1051 """ 1052 return self.__element.index(child.__element) 1053 1054 @property 1055 def text_content(self) -> str: 1056 """Get / set the text of the embedded paragraph, including embeded 1057 annotations, cells... 1058 1059 Set create a paragraph if missing 1060 """ 1061 return "\n".join( 1062 child.text_recursive for child in self.get_elements("descendant::text:p") 1063 ) 1064 1065 @text_content.setter 1066 def text_content(self, text: str | None) -> None: 1067 paragraphs = self.get_elements("text:p") 1068 if not paragraphs: 1069 # E.g., text:p in draw:text-box in draw:frame 1070 paragraphs = self.get_elements("*/text:p") 1071 if paragraphs: 1072 paragraph = paragraphs.pop(0) 1073 for obsolete in paragraphs: 1074 obsolete.delete() 1075 else: 1076 paragraph = Element.from_tag("text:p") 1077 self.insert(paragraph, FIRST_CHILD) 1078 # As "text_content" returned all text nodes, "text_content" 1079 # will overwrite all text nodes and children that may contain them 1080 element = paragraph.__element 1081 # Clear but the attributes 1082 del element[:] 1083 element.text = text 1084 1085 def _erase_text_content(self) -> None: 1086 paragraphs = self.get_elements("text:p") 1087 if not paragraphs: 1088 # E.g., text:p in draw:text-box in draw:frame 1089 paragraphs = self.get_elements("*/text:p") 1090 if paragraphs: 1091 paragraphs.pop(0) 1092 for obsolete in paragraphs: 1093 obsolete.delete() 1094 1095 def is_empty(self) -> bool: 1096 """Check if the element is empty : no text, no children, no tail. 1097 1098 Return: Boolean 1099 """ 1100 element = self.__element 1101 if element.tail is not None: 1102 return False 1103 if element.text is not None: 1104 return False 1105 if list(element.iterchildren()): 1106 return False 1107 return True 1108 1109 def _get_successor(self, target: Element) -> tuple[Element | None, Element | None]: 1110 element = self.__element 1111 next_one = element.getnext() 1112 if next_one is not None: 1113 return Element.from_tag(next_one), target 1114 parent = self.parent 1115 if parent is None: 1116 return None, None 1117 return parent._get_successor(target.parent) # type:ignore 1118 1119 def _get_between_base( # noqa:C901 1120 self, 1121 tag1: Element, 1122 tag2: Element, 1123 ) -> list[Element]: 1124 def find_any_id(elem: Element) -> tuple[str, str, str]: 1125 elem_tag = elem.tag 1126 for attribute in ( 1127 "text:id", 1128 "text:change-id", 1129 "text:name", 1130 "office:name", 1131 "text:ref-name", 1132 "xml:id", 1133 ): 1134 idx = elem.get_attribute(attribute) 1135 if idx is not None: 1136 return elem_tag, attribute, str(idx) 1137 raise ValueError(f"No Id found in {elem.serialize()}") 1138 1139 def common_ancestor( 1140 tag1: str, 1141 attr1: str, 1142 val1: str, 1143 tag2: str, 1144 attr2: str, 1145 val2: str, 1146 ) -> Element | None: 1147 root = self.root 1148 request1 = f'descendant::{tag1}[@{attr1}="{val1}"]' 1149 request2 = f'descendant::{tag2}[@{attr2}="{val2}"]' 1150 ancestor = root.xpath(request1)[0] 1151 if ancestor is None: 1152 return None 1153 while True: 1154 # print "up", 1155 new_ancestor = ancestor.parent 1156 if new_ancestor is None: 1157 return None 1158 has_tag2 = new_ancestor.xpath(request2) 1159 ancestor = new_ancestor 1160 if not has_tag2: 1161 continue 1162 # print 'found' 1163 break 1164 # print up.serialize() 1165 return ancestor 1166 1167 elem1_tag, elem1_attr, elem1_val = find_any_id(tag1) 1168 elem2_tag, elem2_attr, elem2_val = find_any_id(tag2) 1169 ancestor_result = common_ancestor( 1170 elem1_tag, 1171 elem1_attr, 1172 elem1_val, 1173 elem2_tag, 1174 elem2_attr, 1175 elem2_val, 1176 ) 1177 if ancestor_result is None: 1178 raise RuntimeError(f"No common ancestor for {elem1_tag} {elem2_tag}") 1179 ancestor = ancestor_result.clone 1180 path1 = f'{elem1_tag}[@{elem1_attr}="{elem1_val}"]' 1181 path2 = f'{elem2_tag}[@{elem2_attr}="{elem2_val}"]' 1182 result = ancestor.clone 1183 for child in result.children: 1184 result.delete(child) 1185 result.text = "" 1186 result.tail = "" 1187 target = result 1188 current = ancestor.children[0] 1189 1190 state = 0 1191 while True: 1192 if current is None: 1193 raise RuntimeError(f"No current ancestor for {elem1_tag} {elem2_tag}") 1194 # print 'current', state, current.serialize() 1195 if state == 0: # before tag 1 1196 if current.xpath(f"descendant-or-self::{path1}"): 1197 if current.xpath(f"self::{path1}"): 1198 tail = current.tail 1199 if tail: 1200 # got a tail => the parent should be either t:p or t:h 1201 target.text = tail # type: ignore 1202 current, target = current._get_successor(target) # type: ignore 1203 state = 1 1204 continue 1205 # got T1 in chidren, need further analysis 1206 new_target = current.clone 1207 for child in new_target.children: 1208 new_target.delete(child) 1209 new_target.text = "" 1210 new_target.tail = "" 1211 target.append(new_target) # type: ignore 1212 target = new_target 1213 current = current.children[0] 1214 continue 1215 else: 1216 # before tag1 : forget element, go to next one 1217 current, target = current._get_successor(target) # type: ignore 1218 continue 1219 elif state == 1: # collect elements 1220 further = False 1221 if current.xpath(f"descendant-or-self::{path2}"): 1222 if current.xpath(f"self::{path2}"): 1223 # end of trip 1224 break 1225 # got T2 in chidren, need further analysis 1226 further = True 1227 # further analysis needed : 1228 if further: 1229 new_target = current.clone 1230 for child in new_target.children: 1231 new_target.delete(child) 1232 new_target.text = "" 1233 new_target.tail = "" 1234 target.append(new_target) # type: ignore 1235 target = new_target 1236 current = current.children[0] 1237 continue 1238 # collect 1239 target.append(current.clone) # type: ignore 1240 current, target = current._get_successor(target) # type: ignore 1241 continue 1242 # Now resu should be the "parent" of inserted parts 1243 # - a text:h or text:p sigle item (simple case) 1244 # - a upper element, with some text:p, text:h in it => need to be 1245 # stripped to have a list of text:p, text:h 1246 if result.tag in {"text:p", "text:h"}: 1247 inner = [result] 1248 else: 1249 inner = result.children 1250 return inner 1251 1252 def get_between( 1253 self, 1254 tag1: Element, 1255 tag2: Element, 1256 as_text: bool = False, 1257 clean: bool = True, 1258 no_header: bool = True, 1259 ) -> list | Element | str: 1260 """Returns elements between tag1 and tag2, tag1 and tag2 shall 1261 be unique and having an id attribute. 1262 (WARN: buggy if tag1/tag2 defines a malformed odf xml.) 1263 If as_text is True: returns the text content. 1264 If clean is True: suppress unwanted tags (deletions marks, ...) 1265 If no_header is True: existing text:h are changed in text:p 1266 By default: returns a list of Element, cleaned and without headers. 1267 1268 Implementation and standard retrictions: 1269 Only text:h and text:p sould be 'cut' by an insert tag, so inner parts 1270 of insert tags are: 1271 1272 - any text:h, text:p or sub tag of these 1273 1274 - some text, part of a parent text:h or text:p 1275 1276 Arguments: 1277 1278 tag1 -- Element 1279 1280 tag2 -- Element 1281 1282 as_text -- boolean 1283 1284 clean -- boolean 1285 1286 no_header -- boolean 1287 1288 Return: list of odf_paragraph or odf_header 1289 """ 1290 inner = self._get_between_base(tag1, tag2) 1291 1292 if clean: 1293 clean_tags = ( 1294 "text:change", 1295 "text:change-start", 1296 "text:change-end", 1297 "text:reference-mark", 1298 "text:reference-mark-start", 1299 "text:reference-mark-end", 1300 ) 1301 request_self = " | ".join(["self::%s" % c for c in clean_tags]) 1302 inner = [e for e in inner if not e.xpath(request_self)] 1303 request = " | ".join([f"descendant::{tag}" for tag in clean_tags]) 1304 for element in inner: 1305 to_del = element.xpath(request) 1306 for elem in to_del: 1307 if isinstance(elem, Element): 1308 element.delete(elem) 1309 if no_header: # crude replace t:h by t:p 1310 new_inner = [] 1311 for element in inner: 1312 if element.tag == "text:h": 1313 children = element.children 1314 text = element.__element.text 1315 para = Element.from_tag("text:p") 1316 para.text = text or "" 1317 for c in children: 1318 para.append(c) 1319 new_inner.append(para) 1320 else: 1321 new_inner.append(element) 1322 inner = new_inner 1323 if as_text: 1324 return "\n".join([e.get_formatted_text() for e in inner]) 1325 else: 1326 return inner 1327 1328 def insert( 1329 self, 1330 element: Element, 1331 xmlposition: int | None = None, 1332 position: int | None = None, 1333 start: bool = False, 1334 ) -> None: 1335 """Insert an element relatively to ourself. 1336 1337 Insert either using DOM vocabulary or by numeric position. 1338 If text start is True, insert the element before any existing text. 1339 1340 Position start at 0. 1341 1342 Arguments: 1343 1344 element -- Element 1345 1346 xmlposition -- FIRST_CHILD, LAST_CHILD, NEXT_SIBLING 1347 or PREV_SIBLING 1348 1349 start -- Boolean 1350 1351 position -- int 1352 """ 1353 # child_tag = element.tag 1354 current = self.__element 1355 _element = element.__element 1356 if start: 1357 text = current.text 1358 if text is not None: 1359 current.text = None 1360 tail = _element.tail 1361 if tail is None: 1362 tail = text 1363 else: 1364 tail = tail + text 1365 _element.tail = tail 1366 position = 0 1367 if position is not None: 1368 current.insert(position, _element) 1369 elif xmlposition is FIRST_CHILD: 1370 current.insert(0, _element) 1371 elif xmlposition is LAST_CHILD: 1372 current.append(_element) 1373 elif xmlposition is NEXT_SIBLING: 1374 parent = current.getparent() 1375 index = parent.index(current) # type: ignore 1376 parent.insert(index + 1, _element) # type: ignore 1377 elif xmlposition is PREV_SIBLING: 1378 parent = current.getparent() 1379 index = parent.index(current) # type: ignore 1380 parent.insert(index, _element) # type: ignore 1381 else: 1382 raise ValueError("(xml)position must be defined") 1383 1384 def extend(self, odf_elements: Iterable[Element]) -> None: 1385 """Fast append elements at the end of ourself using extend.""" 1386 if odf_elements: 1387 current = self.__element 1388 elements = [element.__element for element in odf_elements] 1389 current.extend(elements) 1390 1391 def append(self, str_or_element: str | Element) -> None: 1392 """Insert element or text in the last position.""" 1393 current = self.__element 1394 if isinstance(str_or_element, str): 1395 # Has children ? 1396 children = list(current.iterchildren()) 1397 if children: 1398 # Append to tail of the last child 1399 last_child = children[-1] 1400 text = last_child.tail 1401 text = text if text is not None else "" 1402 text += str_or_element 1403 last_child.tail = text 1404 else: 1405 # Append to text of the element 1406 text = current.text 1407 text = text if text is not None else "" 1408 text += str_or_element 1409 current.text = text 1410 elif isinstance(str_or_element, Element): 1411 current.append(str_or_element.__element) 1412 else: 1413 raise TypeError(f'Element or string expected, not "{type(str_or_element)}"') 1414 1415 def delete(self, child: Element | None = None, keep_tail: bool = True) -> None: 1416 """Delete the given element from the XML tree. If no element is given, 1417 "self" is deleted. The XML library may allow to continue to use an 1418 element now "orphan" as long as you have a reference to it. 1419 1420 if keep_tail is True (default), the tail text is not erased. 1421 1422 Arguments: 1423 1424 child -- Element 1425 1426 keep_tail -- boolean (default to True), True for most usages. 1427 """ 1428 if child is None: 1429 parent = self.parent 1430 if parent is None: 1431 raise ValueError(f"Can't delete the root element\n{self.serialize()}") 1432 child = self 1433 else: 1434 parent = self 1435 if keep_tail and child.__element.tail is not None: 1436 current = child.__element 1437 tail = str(current.tail) 1438 current.tail = None 1439 prev = current.getprevious() 1440 if prev is not None: 1441 if prev.tail is None: 1442 prev.tail = tail 1443 else: 1444 prev.tail += tail 1445 else: 1446 if parent.__element.text is None: 1447 parent.__element.text = tail 1448 else: 1449 parent.__element.text += tail 1450 parent.__element.remove(child.__element) 1451 1452 def replace_element(self, old_element: Element, new_element: Element) -> None: 1453 """Replaces in place a sub element with the element passed as second 1454 argument. 1455 1456 Warning : no clone for old element. 1457 """ 1458 current = self.__element 1459 current.replace(old_element.__element, new_element.__element) 1460 1461 def strip_elements( 1462 self, 1463 sub_elements: Element | Iterable[Element], 1464 ) -> Element | list: 1465 """Remove the tags of provided elements, keeping inner childs and text. 1466 1467 Return : the striped element. 1468 1469 Warning : no clone in sub_elements list. 1470 1471 Arguments: 1472 1473 sub_elements -- Element or list of Element 1474 """ 1475 if not sub_elements: 1476 return self 1477 if isinstance(sub_elements, Element): 1478 sub_elements = (sub_elements,) 1479 replacer = _get_lxml_tag("text:this-will-be-removed") 1480 for element in sub_elements: 1481 element.__element.tag = replacer 1482 strip = ("text:this-will-be-removed",) 1483 return self.strip_tags(strip=strip, default=None) 1484 1485 def strip_tags( 1486 self, 1487 strip: Iterable[str] | None = None, 1488 protect: Iterable[str] | None = None, 1489 default: str | None = "text:p", 1490 ) -> Element | list: 1491 """Remove the tags listed in strip, recursively, keeping inner childs 1492 and text. Tags listed in protect stop the removal one level depth. If 1493 the first level element is stripped, default is used to embed the 1494 content in the default element. If default is None and first level is 1495 striped, a list of text and children is returned. Return : the striped 1496 element. 1497 1498 strip_tags should be used by on purpose methods (strip_span ...) 1499 (Method name taken from lxml). 1500 1501 Arguments: 1502 1503 strip -- iterable list of str odf tags, or None 1504 1505 protect -- iterable list of str odf tags, or None 1506 1507 default -- str odf tag, or None 1508 1509 Return: 1510 1511 Element. 1512 """ 1513 if not strip: 1514 return self 1515 if not protect: 1516 protect = () 1517 protected = False 1518 element, modified = Element._strip_tags(self, strip, protect, protected) 1519 if modified and isinstance(element, list) and default: 1520 new = Element.from_tag(default) 1521 for content in element: 1522 if isinstance(content, Element): 1523 new.append(content) 1524 else: 1525 new.text = content 1526 element = new 1527 return element 1528 1529 @staticmethod 1530 def _strip_tags( # noqa:C901 1531 element: Element, 1532 strip: Iterable[str], 1533 protect: Iterable[str], 1534 protected: bool, 1535 ) -> tuple[Element | list, bool]: 1536 """Sub method for strip_tags().""" 1537 element_clone = element.clone 1538 modified = False 1539 children = [] 1540 if protect and element.tag in protect: 1541 protect_below = True 1542 else: 1543 protect_below = False 1544 for child in element_clone.children: 1545 striped_child, is_modified = Element._strip_tags( 1546 child, strip, protect, protect_below 1547 ) 1548 if is_modified: 1549 modified = True 1550 if isinstance(striped_child, list): 1551 children.extend(striped_child) 1552 else: 1553 children.append(striped_child) 1554 1555 text = element_clone.text 1556 tail = element_clone.tail 1557 if not protected and strip and element.tag in strip: 1558 element_result: list[Element | str] = [] 1559 if text is not None: 1560 element_result.append(text) 1561 for child in children: 1562 element_result.append(child) 1563 if tail is not None: 1564 element_result.append(tail) 1565 return (element_result, True) 1566 else: 1567 if not modified: 1568 return (element, False) 1569 element.clear() 1570 try: 1571 for key, value in element_clone.attributes.items(): 1572 element.set_attribute(key, value) 1573 except ValueError: 1574 sys.stderr.write(f"strip_tags(): bad attribute in {element_clone}\n") 1575 if text is not None: 1576 element.append(text) 1577 for child in children: 1578 element.append(child) 1579 if tail is not None: 1580 element.tail = tail 1581 return (element, True) 1582 1583 def xpath(self, xpath_query: str) -> list[Element | Text]: 1584 """Apply XPath query to the element and its subtree. Return list of 1585 Element or Text instances translated from the nodes found. 1586 """ 1587 element = self.__element 1588 xpath_instance = xpath_compile(xpath_query) 1589 elements = xpath_instance(element) 1590 result: list[Element | Text] = [] 1591 if hasattr(elements, "__iter__"): 1592 for obj in elements: # type: ignore 1593 if isinstance(obj, (_ElementStringResult, _ElementUnicodeResult)): 1594 result.append(Text(obj)) 1595 elif isinstance(obj, _Element): 1596 result.append(Element.from_tag(obj)) 1597 # else: 1598 # result.append(obj) 1599 return result 1600 1601 def clear(self) -> None: 1602 """Remove text, children and attributes from the element.""" 1603 self.__element.clear() 1604 if hasattr(self, "_tmap"): 1605 self._tmap: list[int] = [] 1606 if hasattr(self, "_cmap"): 1607 self._cmap: list[int] = [] 1608 if hasattr(self, "_rmap"): 1609 self._rmap: list[int] = [] 1610 if hasattr(self, "_indexes"): 1611 remember = False 1612 if "_rmap" in self._indexes: 1613 remember = True 1614 self._indexes: dict[str, dict] = {} 1615 self._indexes["_cmap"] = {} 1616 self._indexes["_tmap"] = {} 1617 if remember: 1618 self._indexes["_rmap"] = {} 1619 1620 @property 1621 def clone(self) -> Element: 1622 clone = deepcopy(self.__element) 1623 root = lxml_Element("ROOT", nsmap=ODF_NAMESPACES) 1624 root.append(clone) 1625 return self.from_tag(clone) 1626 1627 # slow data = tostring(self.__element, encoding='unicode') 1628 # return self.from_tag(data) 1629 1630 @staticmethod 1631 def _strip_namespaces(data: str) -> str: 1632 """Remove xmlns:* fields from serialized XML.""" 1633 return re.sub(r' xmlns:\w*="[\w:\-\/\.#]*"', "", data) 1634 1635 def serialize(self, pretty: bool = False, with_ns: bool = False) -> str: 1636 """Return text serialization of XML element.""" 1637 # This copy bypasses serialization side-effects in lxml 1638 native = deepcopy(self.__element) 1639 data = tostring( 1640 native, with_tail=False, pretty_print=pretty, encoding="unicode" 1641 ) 1642 if with_ns: 1643 return data 1644 # Remove namespaces 1645 return self._strip_namespaces(data) 1646 1647 # Element helpers usable from any context 1648 1649 @property 1650 def document_body(self) -> Element | None: 1651 """Return the document body : 'office:body'""" 1652 return self.get_element("//office:body/*[1]") 1653 1654 @document_body.setter 1655 def document_body(self, new_body: Element) -> None: 1656 """Change in place the full document body content.""" 1657 body = self.document_body 1658 if body is None: 1659 raise ValueError("//office:body not found in document") 1660 tail = body.tail 1661 body.clear() 1662 for item in new_body.children: 1663 body.append(item) 1664 if tail: 1665 body.tail = tail 1666 1667 def get_formatted_text(self, context: dict | None = None) -> str: 1668 """This function should return a beautiful version of the text.""" 1669 return "" 1670 1671 def get_styled_elements(self, name: str = "") -> list[Element]: 1672 """Brute-force to find paragraphs, tables, etc. using the given style 1673 name (or all by default). 1674 1675 Arguments: 1676 1677 name -- str 1678 1679 Return: list 1680 """ 1681 # FIXME incomplete (and possibly inaccurate) 1682 return ( 1683 self._filtered_elements("descendant::*", text_style=name) 1684 + self._filtered_elements("descendant::*", draw_style=name) 1685 + self._filtered_elements("descendant::*", draw_text_style=name) 1686 + self._filtered_elements("descendant::*", table_style=name) 1687 + self._filtered_elements("descendant::*", page_layout=name) 1688 + self._filtered_elements("descendant::*", master_page=name) 1689 + self._filtered_elements("descendant::*", parent_style=name) 1690 ) 1691 1692 # Common attributes 1693 1694 def _get_inner_text(self, tag: str) -> str | None: 1695 element = self.get_element(tag) 1696 if element is None: 1697 return None 1698 return element.text 1699 1700 def _set_inner_text(self, tag: str, text: str) -> None: 1701 element = self.get_element(tag) 1702 if element is None: 1703 element = Element.from_tag(tag) 1704 self.append(element) 1705 element.text = text 1706 1707 # Dublin core 1708 1709 @property 1710 def dc_creator(self) -> str | None: 1711 """Get dc:creator value. 1712 1713 Return: str (or None if inexistant) 1714 """ 1715 return self._get_inner_text("dc:creator") 1716 1717 @dc_creator.setter 1718 def dc_creator(self, creator: str) -> None: 1719 """Set dc:creator value. 1720 1721 Arguments: 1722 1723 creator -- str 1724 """ 1725 self._set_inner_text("dc:creator", creator) 1726 1727 @property 1728 def dc_date(self) -> datetime | None: 1729 """Get the dc:date value. 1730 1731 Return: datetime (or None if inexistant) 1732 """ 1733 date = self._get_inner_text("dc:date") 1734 if date is None: 1735 return None 1736 return DateTime.decode(date) 1737 1738 @dc_date.setter 1739 def dc_date(self, date: datetime) -> None: 1740 """Set the dc:date value. 1741 1742 Arguments: 1743 1744 darz -- datetime 1745 """ 1746 self._set_inner_text("dc:date", DateTime.encode(date)) 1747 1748 # SVG 1749 1750 @property 1751 def svg_title(self) -> str | None: 1752 return self._get_inner_text("svg:title") 1753 1754 @svg_title.setter 1755 def svg_title(self, title: str) -> None: 1756 self._set_inner_text("svg:title", title) 1757 1758 @property 1759 def svg_description(self) -> str | None: 1760 return self._get_inner_text("svg:desc") 1761 1762 @svg_description.setter 1763 def svg_description(self, description: str) -> None: 1764 self._set_inner_text("svg:desc", description) 1765 1766 # Sections 1767 1768 def get_sections( 1769 self, 1770 style: str | None = None, 1771 content: str | None = None, 1772 ) -> list[Element]: 1773 """Return all the sections that match the criteria. 1774 1775 Arguments: 1776 1777 style -- str 1778 1779 content -- str regex 1780 1781 Return: list of Element 1782 """ 1783 return self._filtered_elements( 1784 "text:section", text_style=style, content=content 1785 ) 1786 1787 def get_section( 1788 self, 1789 position: int = 0, 1790 content: str | None = None, 1791 ) -> Element | None: 1792 """Return the section that matches the criteria. 1793 1794 Arguments: 1795 1796 position -- int 1797 1798 content -- str regex 1799 1800 Return: Element or None if not found 1801 """ 1802 return self._filtered_element( 1803 "descendant::text:section", position, content=content 1804 ) 1805 1806 # Paragraphs 1807 1808 def get_paragraphs( 1809 self, 1810 style: str | None = None, 1811 content: str | None = None, 1812 ) -> list[Element]: 1813 """Return all the paragraphs that match the criteria. 1814 1815 Arguments: 1816 1817 style -- str 1818 1819 content -- str regex 1820 1821 Return: list of Paragraph 1822 """ 1823 return self._filtered_elements( 1824 "descendant::text:p", text_style=style, content=content 1825 ) 1826 1827 def get_paragraph( 1828 self, 1829 position: int = 0, 1830 content: str | None = None, 1831 ) -> Element | None: 1832 """Return the paragraph that matches the criteria. 1833 1834 Arguments: 1835 1836 position -- int 1837 1838 content -- str regex 1839 1840 Return: Paragraph or None if not found 1841 """ 1842 return self._filtered_element("descendant::text:p", position, content=content) 1843 1844 # Span 1845 1846 def get_spans( 1847 self, 1848 style: str | None = None, 1849 content: str | None = None, 1850 ) -> list[Element]: 1851 """Return all the spans that match the criteria. 1852 1853 Arguments: 1854 1855 style -- str 1856 1857 content -- str regex 1858 1859 Return: list of Span 1860 """ 1861 return self._filtered_elements( 1862 "descendant::text:span", text_style=style, content=content 1863 ) 1864 1865 def get_span( 1866 self, 1867 position: int = 0, 1868 content: str | None = None, 1869 ) -> Element | None: 1870 """Return the span that matches the criteria. 1871 1872 Arguments: 1873 1874 position -- int 1875 1876 content -- str regex 1877 1878 Return: Span or None if not found 1879 """ 1880 return self._filtered_element( 1881 "descendant::text:span", position, content=content 1882 ) 1883 1884 # Headers 1885 1886 def get_headers( 1887 self, 1888 style: str | None = None, 1889 outline_level: str | None = None, 1890 content: str | None = None, 1891 ) -> list[Element]: 1892 """Return all the Headers that match the criteria. 1893 1894 Arguments: 1895 1896 style -- str 1897 1898 content -- str regex 1899 1900 Return: list of Header 1901 """ 1902 return self._filtered_elements( 1903 "descendant::text:h", 1904 text_style=style, 1905 outline_level=outline_level, 1906 content=content, 1907 ) 1908 1909 def get_header( 1910 self, 1911 position: int = 0, 1912 outline_level: str | None = None, 1913 content: str | None = None, 1914 ) -> Element | None: 1915 """Return the Header that matches the criteria. 1916 1917 Arguments: 1918 1919 position -- int 1920 1921 content -- str regex 1922 1923 Return: Header or None if not found 1924 """ 1925 return self._filtered_element( 1926 "descendant::text:h", 1927 position, 1928 outline_level=outline_level, 1929 content=content, 1930 ) 1931 1932 # Lists 1933 1934 def get_lists( 1935 self, 1936 style: str | None = None, 1937 content: str | None = None, 1938 ) -> list[Element]: 1939 """Return all the lists that match the criteria. 1940 1941 Arguments: 1942 1943 style -- str 1944 1945 content -- str regex 1946 1947 Return: list of List 1948 """ 1949 return self._filtered_elements( 1950 "descendant::text:list", text_style=style, content=content 1951 ) 1952 1953 def get_list( 1954 self, 1955 position: int = 0, 1956 content: str | None = None, 1957 ) -> Element | None: 1958 """Return the list that matches the criteria. 1959 1960 Arguments: 1961 1962 position -- int 1963 1964 content -- str regex 1965 1966 Return: List or None if not found 1967 """ 1968 return self._filtered_element( 1969 "descendant::text:list", position, content=content 1970 ) 1971 1972 # Frames 1973 1974 def get_frames( 1975 self, 1976 presentation_class: str | None = None, 1977 style: str | None = None, 1978 title: str | None = None, 1979 description: str | None = None, 1980 content: str | None = None, 1981 ) -> list[Element]: 1982 """Return all the frames that match the criteria. 1983 1984 Arguments: 1985 1986 presentation_class -- str 1987 1988 style -- str 1989 1990 title -- str regex 1991 1992 description -- str regex 1993 1994 content -- str regex 1995 1996 Return: list of Frame 1997 """ 1998 return self._filtered_elements( 1999 "descendant::draw:frame", 2000 presentation_class=presentation_class, 2001 draw_style=style, 2002 svg_title=title, 2003 svg_desc=description, 2004 content=content, 2005 ) 2006 2007 def get_frame( 2008 self, 2009 position: int = 0, 2010 name: str | None = None, 2011 presentation_class: str | None = None, 2012 title: str | None = None, 2013 description: str | None = None, 2014 content: str | None = None, 2015 ) -> Element | None: 2016 """Return the section that matches the criteria. 2017 2018 Arguments: 2019 2020 position -- int 2021 2022 name -- str 2023 2024 presentation_class -- str 2025 2026 title -- str regex 2027 2028 description -- str regex 2029 2030 content -- str regex 2031 2032 Return: Frame or None if not found 2033 """ 2034 return self._filtered_element( 2035 "descendant::draw:frame", 2036 position, 2037 draw_name=name, 2038 presentation_class=presentation_class, 2039 svg_title=title, 2040 svg_desc=description, 2041 content=content, 2042 ) 2043 2044 # Images 2045 2046 def get_images( 2047 self, 2048 style: str | None = None, 2049 url: str | None = None, 2050 content: str | None = None, 2051 ) -> list[Element]: 2052 """Return all the images matching the criteria. 2053 2054 Arguments: 2055 2056 style -- str 2057 2058 url -- str regex 2059 2060 content -- str regex 2061 2062 Return: list of Element 2063 """ 2064 return self._filtered_elements( 2065 "descendant::draw:image", text_style=style, url=url, content=content 2066 ) 2067 2068 def get_image( 2069 self, 2070 position: int = 0, 2071 name: str | None = None, 2072 url: str | None = None, 2073 content: str | None = None, 2074 ) -> Element | None: 2075 """Return the image matching the criteria. 2076 2077 Arguments: 2078 2079 position -- int 2080 2081 name -- str 2082 2083 url -- str regex 2084 2085 content -- str regex 2086 2087 Return: Element or None if not found 2088 """ 2089 # The frame is holding the name 2090 if name is not None: 2091 frame = self._filtered_element( 2092 "descendant::draw:frame", position, draw_name=name 2093 ) 2094 if frame is None: 2095 return None 2096 # The name is supposedly unique 2097 return frame.get_element("draw:image") 2098 return self._filtered_element( 2099 "descendant::draw:image", position, url=url, content=content 2100 ) 2101 2102 # Tables 2103 2104 def get_tables( 2105 self, 2106 style: str | None = None, 2107 content: str | None = None, 2108 ) -> list[Element]: 2109 """Return all the tables that match the criteria. 2110 2111 Arguments: 2112 2113 style -- str 2114 2115 content -- str regex 2116 2117 Return: list of Table 2118 """ 2119 return self._filtered_elements( 2120 "descendant::table:table", table_style=style, content=content 2121 ) 2122 2123 def get_table( 2124 self, 2125 position: int = 0, 2126 name: str | None = None, 2127 content: str | None = None, 2128 ) -> Element | None: 2129 """Return the table that matches the criteria. 2130 2131 Arguments: 2132 2133 position -- int 2134 2135 name -- str 2136 2137 content -- str regex 2138 2139 Return: Table or None if not found 2140 """ 2141 if name is None and content is None: 2142 result = self._filtered_element("descendant::table:table", position) 2143 else: 2144 result = self._filtered_element( 2145 "descendant::table:table", 2146 position, 2147 table_name=name, 2148 content=content, 2149 ) 2150 return result 2151 2152 # Named Range 2153 2154 def get_named_ranges(self) -> list[Element]: 2155 """Return all the tables named ranges. 2156 2157 Return: list of odf_named_range 2158 """ 2159 named_ranges = self.get_elements( 2160 "descendant::table:named-expressions/table:named-range" 2161 ) 2162 return named_ranges 2163 2164 def get_named_range(self, name: str) -> Element | None: 2165 """Return the named range of specified name, or None if not found. 2166 2167 Arguments: 2168 2169 name -- str 2170 2171 Return: NamedRange 2172 """ 2173 named_range = self.get_elements( 2174 f'descendant::table:named-expressions/table:named-range[@table:name="{name}"][1]' 2175 ) 2176 if named_range: 2177 return named_range[0] 2178 else: 2179 return None 2180 2181 def append_named_range(self, named_range: Element) -> None: 2182 """Append the named range to the spreadsheet, replacing existing named 2183 range of same name if any. 2184 2185 Arguments: 2186 2187 named_range -- NamedRange 2188 """ 2189 if self.tag != "office:spreadsheet": 2190 raise ValueError(f"Element is no 'office:spreadsheet' : {self.tag}") 2191 named_expressions = self.get_element("table:named-expressions") 2192 if not named_expressions: 2193 named_expressions = Element.from_tag("table:named-expressions") 2194 self.append(named_expressions) 2195 # exists ? 2196 current = named_expressions.get_element( 2197 f'table:named-range[@table:name="{named_range.name}"][1]' # type:ignore 2198 ) 2199 if current: 2200 named_expressions.delete(current) 2201 named_expressions.append(named_range) 2202 2203 def delete_named_range(self, name: str) -> None: 2204 """Delete the Named Range of specified name from the spreadsheet. 2205 2206 Arguments: 2207 2208 name -- str 2209 """ 2210 if self.tag != "office:spreadsheet": 2211 raise ValueError(f"Element is no 'office:spreadsheet' : {self.tag}") 2212 named_range = self.get_named_range(name) 2213 if not named_range: 2214 return 2215 named_range.delete() 2216 named_expressions = self.get_element("table:named-expressions") 2217 if not named_expressions: 2218 return 2219 element = named_expressions.__element 2220 children = list(element.iterchildren()) 2221 if not children: 2222 self.delete(named_expressions) 2223 2224 # Notes 2225 2226 def get_notes( 2227 self, 2228 note_class: str | None = None, 2229 content: str | None = None, 2230 ) -> list[Element]: 2231 """Return all the notes that match the criteria. 2232 2233 Arguments: 2234 2235 note_class -- 'footnote' or 'endnote' 2236 2237 content -- str regex 2238 2239 Return: list of Note 2240 """ 2241 return self._filtered_elements( 2242 "descendant::text:note", note_class=note_class, content=content 2243 ) 2244 2245 def get_note( 2246 self, 2247 position: int = 0, 2248 note_id: str | None = None, 2249 note_class: str | None = None, 2250 content: str | None = None, 2251 ) -> Element | None: 2252 """Return the note that matches the criteria. 2253 2254 Arguments: 2255 2256 position -- int 2257 2258 note_id -- str 2259 2260 note_class -- 'footnote' or 'endnote' 2261 2262 content -- str regex 2263 2264 Return: Note or None if not found 2265 """ 2266 return self._filtered_element( 2267 "descendant::text:note", 2268 position, 2269 text_id=note_id, 2270 note_class=note_class, 2271 content=content, 2272 ) 2273 2274 # Annotations 2275 2276 def get_annotations( 2277 self, 2278 creator: str | None = None, 2279 start_date: datetime | None = None, 2280 end_date: datetime | None = None, 2281 content: str | None = None, 2282 ) -> list[Element]: 2283 """Return all the annotations that match the criteria. 2284 2285 Arguments: 2286 2287 creator -- str 2288 2289 start_date -- datetime instance 2290 2291 end_date -- datetime instance 2292 2293 content -- str regex 2294 2295 Return: list of Annotation 2296 """ 2297 annotations = [] 2298 for annotation in self._filtered_elements( 2299 "descendant::office:annotation", content=content 2300 ): 2301 if creator is not None and creator != annotation.dc_creator: 2302 continue 2303 date = annotation.dc_date 2304 if date is None: 2305 continue 2306 if start_date is not None and date < start_date: 2307 continue 2308 if end_date is not None and date >= end_date: 2309 continue 2310 annotations.append(annotation) 2311 return annotations 2312 2313 def get_annotation( 2314 self, 2315 position: int = 0, 2316 creator: str | None = None, 2317 start_date: datetime | None = None, 2318 end_date: datetime | None = None, 2319 content: str | None = None, 2320 name: str | None = None, 2321 ) -> Element | None: 2322 """Return the annotation that matches the criteria. 2323 2324 Arguments: 2325 2326 position -- int 2327 2328 creator -- str 2329 2330 start_date -- datetime instance 2331 2332 end_date -- datetime instance 2333 2334 content -- str regex 2335 2336 name -- str 2337 2338 Return: Annotation or None if not found 2339 """ 2340 if name is not None: 2341 return self._filtered_element( 2342 "descendant::office:annotation", 0, office_name=name 2343 ) 2344 annotations = self.get_annotations( 2345 creator=creator, start_date=start_date, end_date=end_date, content=content 2346 ) 2347 if not annotations: 2348 return None 2349 try: 2350 return annotations[position] 2351 except IndexError: 2352 return None 2353 2354 def get_annotation_ends(self) -> list[Element]: 2355 """Return all the annotation ends. 2356 2357 Return: list of Element 2358 """ 2359 return self._filtered_elements("descendant::office:annotation-end") 2360 2361 def get_annotation_end( 2362 self, 2363 position: int = 0, 2364 name: str | None = None, 2365 ) -> Element | None: 2366 """Return the annotation end that matches the criteria. 2367 2368 Arguments: 2369 2370 position -- int 2371 2372 name -- str 2373 2374 Return: Element or None if not found 2375 """ 2376 return self._filtered_element( 2377 "descendant::office:annotation-end", position, office_name=name 2378 ) 2379 2380 # office:names 2381 2382 def get_office_names(self) -> list[str]: 2383 """Return all the used office:name tags values of the element. 2384 2385 Return: list of unique str 2386 """ 2387 name_xpath_query = xpath_compile("//@office:name") 2388 response = name_xpath_query(self.__element) 2389 if not isinstance(response, list): 2390 return [] 2391 return list({str(name) for name in response if name}) 2392 2393 # Variables 2394 2395 def get_variable_decls(self) -> Element: 2396 """Return the container for variable declarations. Created if not 2397 found. 2398 2399 Return: Element 2400 """ 2401 variable_decls = self.get_element("//text:variable-decls") 2402 if variable_decls is None: 2403 body = self.document_body 2404 if not body: 2405 raise ValueError("Empty document.body") 2406 body.insert(Element.from_tag("text:variable-decls"), FIRST_CHILD) 2407 variable_decls = body.get_element("//text:variable-decls") 2408 2409 return variable_decls # type:ignore 2410 2411 def get_variable_decl_list(self) -> list[Element]: 2412 """Return all the variable declarations. 2413 2414 Return: list of Element 2415 """ 2416 return self._filtered_elements("descendant::text:variable-decl") 2417 2418 def get_variable_decl(self, name: str, position: int = 0) -> Element | None: 2419 """return the variable declaration for the given name. 2420 2421 Arguments: 2422 2423 name -- str 2424 2425 position -- int 2426 2427 return: Element or none if not found 2428 """ 2429 return self._filtered_element( 2430 "descendant::text:variable-decl", position, text_name=name 2431 ) 2432 2433 def get_variable_sets(self, name: str | None = None) -> list[Element]: 2434 """Return all the variable sets that match the criteria. 2435 2436 Arguments: 2437 2438 name -- str 2439 2440 Return: list of Element 2441 """ 2442 return self._filtered_elements("descendant::text:variable-set", text_name=name) 2443 2444 def get_variable_set(self, name: str, position: int = -1) -> Element | None: 2445 """Return the variable set for the given name (last one by default). 2446 2447 Arguments: 2448 2449 name -- str 2450 2451 position -- int 2452 2453 Return: Element or None if not found 2454 """ 2455 return self._filtered_element( 2456 "descendant::text:variable-set", position, text_name=name 2457 ) 2458 2459 def get_variable_set_value( 2460 self, 2461 name: str, 2462 value_type: str | None = None, 2463 ) -> bool | str | int | float | Decimal | datetime | timedelta | None: 2464 """Return the last value of the given variable name. 2465 2466 Arguments: 2467 2468 name -- str 2469 2470 value_type -- 'boolean', 'currency', 'date', 'float', 2471 'percentage', 'string', 'time' or automatic 2472 2473 Return: most appropriate Python type 2474 """ 2475 variable_set = self.get_variable_set(name) 2476 if not variable_set: 2477 return None 2478 return variable_set.get_value(value_type) # type: ignore 2479 2480 # User fields 2481 2482 def get_user_field_decls(self) -> Element | None: 2483 """Return the container for user field declarations. Created if not 2484 found. 2485 2486 Return: Element 2487 """ 2488 user_field_decls = self.get_element("//text:user-field-decls") 2489 if user_field_decls is None: 2490 body = self.document_body 2491 if not body: 2492 raise ValueError("Empty document.body") 2493 body.insert(Element.from_tag("text:user-field-decls"), FIRST_CHILD) 2494 user_field_decls = body.get_element("//text:user-field-decls") 2495 2496 return user_field_decls 2497 2498 def get_user_field_decl_list(self) -> list[Element]: 2499 """Return all the user field declarations. 2500 2501 Return: list of Element 2502 """ 2503 return self._filtered_elements("descendant::text:user-field-decl") 2504 2505 def get_user_field_decl(self, name: str, position: int = 0) -> Element | None: 2506 """return the user field declaration for the given name. 2507 2508 return: Element or none if not found 2509 """ 2510 return self._filtered_element( 2511 "descendant::text:user-field-decl", position, text_name=name 2512 ) 2513 2514 def get_user_field_value( 2515 self, name: str, value_type: str | None = None 2516 ) -> bool | str | int | float | Decimal | datetime | timedelta | None: 2517 """Return the value of the given user field name. 2518 2519 Arguments: 2520 2521 name -- str 2522 2523 value_type -- 'boolean', 'currency', 'date', 'float', 2524 'percentage', 'string', 'time' or automatic 2525 2526 Return: most appropriate Python type 2527 """ 2528 user_field_decl = self.get_user_field_decl(name) 2529 if user_field_decl is None: 2530 return None 2531 return user_field_decl.get_value(value_type) # type: ignore 2532 2533 # User defined fields 2534 # They are fields who should contain a copy of a user defined medtadata 2535 2536 def get_user_defined_list(self) -> list[Element]: 2537 """Return all the user defined field declarations. 2538 2539 Return: list of Element 2540 """ 2541 return self._filtered_elements("descendant::text:user-defined") 2542 2543 def get_user_defined(self, name: str, position: int = 0) -> Element | None: 2544 """return the user defined declaration for the given name. 2545 2546 return: Element or none if not found 2547 """ 2548 return self._filtered_element( 2549 "descendant::text:user-defined", position, text_name=name 2550 ) 2551 2552 def get_user_defined_value( 2553 self, name: str, value_type: str | None = None 2554 ) -> bool | str | int | float | Decimal | datetime | timedelta | None: 2555 """Return the value of the given user defined field name. 2556 2557 Arguments: 2558 2559 name -- str 2560 2561 value_type -- 'boolean', 'date', 'float', 2562 'string', 'time' or automatic 2563 2564 Return: most appropriate Python type 2565 """ 2566 user_defined = self.get_user_defined(name) 2567 if user_defined is None: 2568 return None 2569 return user_defined.get_value(value_type) # type: ignore 2570 2571 # Draw Pages 2572 2573 def get_draw_pages( 2574 self, 2575 style: str | None = None, 2576 content: str | None = None, 2577 ) -> list[Element]: 2578 """Return all the draw pages that match the criteria. 2579 2580 Arguments: 2581 2582 style -- str 2583 2584 content -- str regex 2585 2586 Return: list of DrawPage 2587 """ 2588 return self._filtered_elements( 2589 "descendant::draw:page", draw_style=style, content=content 2590 ) 2591 2592 def get_draw_page( 2593 self, 2594 position: int = 0, 2595 name: str | None = None, 2596 content: str | None = None, 2597 ) -> Element | None: 2598 """Return the draw page that matches the criteria. 2599 2600 Arguments: 2601 2602 position -- int 2603 2604 name -- str 2605 2606 content -- str regex 2607 2608 Return: DrawPage or None if not found 2609 """ 2610 return self._filtered_element( 2611 "descendant::draw:page", position, draw_name=name, content=content 2612 ) 2613 2614 # Links 2615 2616 def get_links( 2617 self, 2618 name: str | None = None, 2619 title: str | None = None, 2620 url: str | None = None, 2621 content: str | None = None, 2622 ) -> list[Element]: 2623 """Return all the links that match the criteria. 2624 2625 Arguments: 2626 2627 name -- str 2628 2629 title -- str 2630 2631 url -- str regex 2632 2633 content -- str regex 2634 2635 Return: list of Element 2636 """ 2637 return self._filtered_elements( 2638 "descendant::text:a", 2639 office_name=name, 2640 office_title=title, 2641 url=url, 2642 content=content, 2643 ) 2644 2645 def get_link( 2646 self, 2647 position: int = 0, 2648 name: str | None = None, 2649 title: str | None = None, 2650 url: str | None = None, 2651 content: str | None = None, 2652 ) -> Element | None: 2653 """Return the link that matches the criteria. 2654 2655 Arguments: 2656 2657 position -- int 2658 2659 name -- str 2660 2661 title -- str 2662 2663 url -- str regex 2664 2665 content -- str regex 2666 2667 Return: Element or None if not found 2668 """ 2669 return self._filtered_element( 2670 "descendant::text:a", 2671 position, 2672 office_name=name, 2673 office_title=title, 2674 url=url, 2675 content=content, 2676 ) 2677 2678 # Bookmarks 2679 2680 def get_bookmarks(self) -> list[Element]: 2681 """Return all the bookmarks. 2682 2683 Return: list of Element 2684 """ 2685 return self._filtered_elements("descendant::text:bookmark") 2686 2687 def get_bookmark( 2688 self, 2689 position: int = 0, 2690 name: str | None = None, 2691 ) -> Element | None: 2692 """Return the bookmark that matches the criteria. 2693 2694 Arguments: 2695 2696 position -- int 2697 2698 name -- str 2699 2700 Return: Bookmark or None if not found 2701 """ 2702 return self._filtered_element( 2703 "descendant::text:bookmark", position, text_name=name 2704 ) 2705 2706 def get_bookmark_starts(self) -> list[Element]: 2707 """Return all the bookmark starts. 2708 2709 Return: list of Element 2710 """ 2711 return self._filtered_elements("descendant::text:bookmark-start") 2712 2713 def get_bookmark_start( 2714 self, 2715 position: int = 0, 2716 name: str | None = None, 2717 ) -> Element | None: 2718 """Return the bookmark start that matches the criteria. 2719 2720 Arguments: 2721 2722 position -- int 2723 2724 name -- str 2725 2726 Return: Element or None if not found 2727 """ 2728 return self._filtered_element( 2729 "descendant::text:bookmark-start", position, text_name=name 2730 ) 2731 2732 def get_bookmark_ends(self) -> list[Element]: 2733 """Return all the bookmark ends. 2734 2735 Return: list of Element 2736 """ 2737 return self._filtered_elements("descendant::text:bookmark-end") 2738 2739 def get_bookmark_end( 2740 self, 2741 position: int = 0, 2742 name: str | None = None, 2743 ) -> Element | None: 2744 """Return the bookmark end that matches the criteria. 2745 2746 Arguments: 2747 2748 position -- int 2749 2750 name -- str 2751 2752 Return: Element or None if not found 2753 """ 2754 return self._filtered_element( 2755 "descendant::text:bookmark-end", position, text_name=name 2756 ) 2757 2758 # Reference marks 2759 2760 def get_reference_marks_single(self) -> list[Element]: 2761 """Return all the reference marks. Search only the tags 2762 text:reference-mark. 2763 Consider using : get_reference_marks() 2764 2765 Return: list of Element 2766 """ 2767 return self._filtered_elements("descendant::text:reference-mark") 2768 2769 def get_reference_mark_single( 2770 self, 2771 position: int = 0, 2772 name: str | None = None, 2773 ) -> Element | None: 2774 """Return the reference mark that matches the criteria. Search only the 2775 tags text:reference-mark. 2776 Consider using : get_reference_mark() 2777 2778 Arguments: 2779 2780 position -- int 2781 2782 name -- str 2783 2784 Return: Element or None if not found 2785 """ 2786 return self._filtered_element( 2787 "descendant::text:reference-mark", position, text_name=name 2788 ) 2789 2790 def get_reference_mark_starts(self) -> list[Element]: 2791 """Return all the reference mark starts. Search only the tags 2792 text:reference-mark-start. 2793 Consider using : get_reference_marks() 2794 2795 Return: list of Element 2796 """ 2797 return self._filtered_elements("descendant::text:reference-mark-start") 2798 2799 def get_reference_mark_start( 2800 self, 2801 position: int = 0, 2802 name: str | None = None, 2803 ) -> Element | None: 2804 """Return the reference mark start that matches the criteria. Search 2805 only the tags text:reference-mark-start. 2806 Consider using : get_reference_mark() 2807 2808 Arguments: 2809 2810 position -- int 2811 2812 name -- str 2813 2814 Return: Element or None if not found 2815 """ 2816 return self._filtered_element( 2817 "descendant::text:reference-mark-start", position, text_name=name 2818 ) 2819 2820 def get_reference_mark_ends(self) -> list[Element]: 2821 """Return all the reference mark ends. Search only the tags 2822 text:reference-mark-end. 2823 Consider using : get_reference_marks() 2824 2825 Return: list of Element 2826 """ 2827 return self._filtered_elements("descendant::text:reference-mark-end") 2828 2829 def get_reference_mark_end( 2830 self, 2831 position: int = 0, 2832 name: str | None = None, 2833 ) -> Element | None: 2834 """Return the reference mark end that matches the criteria. Search only 2835 the tags text:reference-mark-end. 2836 Consider using : get_reference_marks() 2837 2838 Arguments: 2839 2840 position -- int 2841 2842 name -- str 2843 2844 Return: Element or None if not found 2845 """ 2846 return self._filtered_element( 2847 "descendant::text:reference-mark-end", position, text_name=name 2848 ) 2849 2850 def get_reference_marks(self) -> list[Element]: 2851 """Return all the reference marks, either single position reference 2852 (text:reference-mark) or start of range reference 2853 (text:reference-mark-start). 2854 2855 Return: list of Element 2856 """ 2857 return self._filtered_elements( 2858 "descendant::text:reference-mark-start | descendant::text:reference-mark" 2859 ) 2860 2861 def get_reference_mark( 2862 self, 2863 position: int = 0, 2864 name: str | None = None, 2865 ) -> Element | None: 2866 """Return the reference mark that match the criteria. Either single 2867 position reference mark (text:reference-mark) or start of range 2868 reference (text:reference-mark-start). 2869 2870 Arguments: 2871 2872 position -- int 2873 2874 name -- str 2875 2876 Return: Element or None if not found 2877 """ 2878 if name: 2879 request = ( 2880 f"descendant::text:reference-mark-start" 2881 f'[@text:name="{name}"] ' 2882 f"| descendant::text:reference-mark" 2883 f'[@text:name="{name}"]' 2884 ) 2885 return self._filtered_element(request, position=0) 2886 request = ( 2887 "descendant::text:reference-mark-start | descendant::text:reference-mark" 2888 ) 2889 return self._filtered_element(request, position) 2890 2891 def get_references(self, name: str | None = None) -> list[Element]: 2892 """Return all the references (text:reference-ref). If name is 2893 provided, returns the references of that name. 2894 2895 Return: list of Element 2896 2897 Arguments: 2898 2899 name -- str or None 2900 """ 2901 if name is None: 2902 return self._filtered_elements("descendant::text:reference-ref") 2903 request = f'descendant::text:reference-ref[@text:ref-name="{name}"]' 2904 return self._filtered_elements(request) 2905 2906 # Shapes elements 2907 2908 # Groups 2909 2910 def get_draw_groups( 2911 self, 2912 title: str | None = None, 2913 description: str | None = None, 2914 content: str | None = None, 2915 ) -> list[Element]: 2916 return self._filtered_elements( 2917 "descendant::draw:g", 2918 svg_title=title, 2919 svg_desc=description, 2920 content=content, 2921 ) 2922 2923 def get_draw_group( 2924 self, 2925 position: int = 0, 2926 name: str | None = None, 2927 title: str | None = None, 2928 description: str | None = None, 2929 content: str | None = None, 2930 ) -> Element | None: 2931 return self._filtered_element( 2932 "descendant::draw:g", 2933 position, 2934 draw_name=name, 2935 svg_title=title, 2936 svg_desc=description, 2937 content=content, 2938 ) 2939 2940 # Lines 2941 2942 def get_draw_lines( 2943 self, 2944 draw_style: str | None = None, 2945 draw_text_style: str | None = None, 2946 content: str | None = None, 2947 ) -> list[Element]: 2948 """Return all the draw lines that match the criteria. 2949 2950 Arguments: 2951 2952 draw_style -- str 2953 2954 draw_text_style -- str 2955 2956 content -- str regex 2957 2958 Return: list of odf_shape 2959 """ 2960 return self._filtered_elements( 2961 "descendant::draw:line", 2962 draw_style=draw_style, 2963 draw_text_style=draw_text_style, 2964 content=content, 2965 ) 2966 2967 def get_draw_line( 2968 self, 2969 position: int = 0, 2970 id: str | None = None, # noqa:A002 2971 content: str | None = None, 2972 ) -> Element | None: 2973 """Return the draw line that matches the criteria. 2974 2975 Arguments: 2976 2977 position -- int 2978 2979 id -- str 2980 2981 content -- str regex 2982 2983 Return: odf_shape or None if not found 2984 """ 2985 return self._filtered_element( 2986 "descendant::draw:line", position, draw_id=id, content=content 2987 ) 2988 2989 # Rectangles 2990 2991 def get_draw_rectangles( 2992 self, 2993 draw_style: str | None = None, 2994 draw_text_style: str | None = None, 2995 content: str | None = None, 2996 ) -> list[Element]: 2997 """Return all the draw rectangles that match the criteria. 2998 2999 Arguments: 3000 3001 draw_style -- str 3002 3003 draw_text_style -- str 3004 3005 content -- str regex 3006 3007 Return: list of odf_shape 3008 """ 3009 return self._filtered_elements( 3010 "descendant::draw:rect", 3011 draw_style=draw_style, 3012 draw_text_style=draw_text_style, 3013 content=content, 3014 ) 3015 3016 def get_draw_rectangle( 3017 self, 3018 position: int = 0, 3019 id: str | None = None, # noqa:A002 3020 content: str | None = None, 3021 ) -> Element | None: 3022 """Return the draw rectangle that matches the criteria. 3023 3024 Arguments: 3025 3026 position -- int 3027 3028 id -- str 3029 3030 content -- str regex 3031 3032 Return: odf_shape or None if not found 3033 """ 3034 return self._filtered_element( 3035 "descendant::draw:rect", position, draw_id=id, content=content 3036 ) 3037 3038 # Ellipse 3039 3040 def get_draw_ellipses( 3041 self, 3042 draw_style: str | None = None, 3043 draw_text_style: str | None = None, 3044 content: str | None = None, 3045 ) -> list[Element]: 3046 """Return all the draw ellipses that match the criteria. 3047 3048 Arguments: 3049 3050 draw_style -- str 3051 3052 draw_text_style -- str 3053 3054 content -- str regex 3055 3056 Return: list of odf_shape 3057 """ 3058 return self._filtered_elements( 3059 "descendant::draw:ellipse", 3060 draw_style=draw_style, 3061 draw_text_style=draw_text_style, 3062 content=content, 3063 ) 3064 3065 def get_draw_ellipse( 3066 self, 3067 position: int = 0, 3068 id: str | None = None, # noqa:A002 3069 content: str | None = None, 3070 ) -> Element | None: 3071 """Return the draw ellipse that matches the criteria. 3072 3073 Arguments: 3074 3075 position -- int 3076 3077 id -- str 3078 3079 content -- str regex 3080 3081 Return: odf_shape or None if not found 3082 """ 3083 return self._filtered_element( 3084 "descendant::draw:ellipse", position, draw_id=id, content=content 3085 ) 3086 3087 # Connectors 3088 3089 def get_draw_connectors( 3090 self, 3091 draw_style: str | None = None, 3092 draw_text_style: str | None = None, 3093 content: str | None = None, 3094 ) -> list[Element]: 3095 """Return all the draw connectors that match the criteria. 3096 3097 Arguments: 3098 3099 draw_style -- str 3100 3101 draw_text_style -- str 3102 3103 content -- str regex 3104 3105 Return: list of odf_shape 3106 """ 3107 return self._filtered_elements( 3108 "descendant::draw:connector", 3109 draw_style=draw_style, 3110 draw_text_style=draw_text_style, 3111 content=content, 3112 ) 3113 3114 def get_draw_connector( 3115 self, 3116 position: int = 0, 3117 id: str | None = None, # noqa:A002 3118 content: str | None = None, 3119 ) -> Element | None: 3120 """Return the draw connector that matches the criteria. 3121 3122 Arguments: 3123 3124 position -- int 3125 3126 id -- str 3127 3128 content -- str regex 3129 3130 Return: odf_shape or None if not found 3131 """ 3132 return self._filtered_element( 3133 "descendant::draw:connector", position, draw_id=id, content=content 3134 ) 3135 3136 def get_orphan_draw_connectors(self) -> list[Element]: 3137 """Return a list of connectors which don't have any shape connected 3138 to them. 3139 """ 3140 connectors = [] 3141 for connector in self.get_draw_connectors(): 3142 start_shape = connector.get_attribute("draw:start-shape") 3143 end_shape = connector.get_attribute("draw:end-shape") 3144 if start_shape is None and end_shape is None: 3145 connectors.append(connector) 3146 return connectors 3147 3148 # Tracked changes and text change 3149 3150 def get_tracked_changes(self) -> Element | None: 3151 """Return the tracked-changes part in the text body.""" 3152 return self.get_element("//text:tracked-changes") 3153 3154 def get_changes_ids(self) -> list[Element | Text]: 3155 """Return a list of ids that refers to a change region in the tracked 3156 changes list. 3157 """ 3158 # Insertion changes 3159 xpath_query = "descendant::text:change-start/@text:change-id" 3160 # Deletion changes 3161 xpath_query += " | descendant::text:change/@text:change-id" 3162 return self.xpath(xpath_query) 3163 3164 def get_text_change_deletions(self) -> list[Element]: 3165 """Return all the text changes of deletion kind: the tags text:change. 3166 Consider using : get_text_changes() 3167 3168 Return: list of Element 3169 """ 3170 return self._filtered_elements("descendant::text:text:change") 3171 3172 def get_text_change_deletion( 3173 self, 3174 position: int = 0, 3175 idx: str | None = None, 3176 ) -> Element | None: 3177 """Return the text change of deletion kind that matches the criteria. 3178 Search only for the tags text:change. 3179 Consider using : get_text_change() 3180 3181 Arguments: 3182 3183 position -- int 3184 3185 idx -- str 3186 3187 Return: Element or None if not found 3188 """ 3189 return self._filtered_element( 3190 "descendant::text:change", position, change_id=idx 3191 ) 3192 3193 def get_text_change_starts(self) -> list[Element]: 3194 """Return all the text change-start. Search only for the tags 3195 text:change-start. 3196 Consider using : get_text_changes() 3197 3198 Return: list of Element 3199 """ 3200 return self._filtered_elements("descendant::text:change-start") 3201 3202 def get_text_change_start( 3203 self, 3204 position: int = 0, 3205 idx: str | None = None, 3206 ) -> Element | None: 3207 """Return the text change-start that matches the criteria. Search 3208 only the tags text:change-start. 3209 Consider using : get_text_change() 3210 3211 Arguments: 3212 3213 position -- int 3214 3215 idx -- str 3216 3217 Return: Element or None if not found 3218 """ 3219 return self._filtered_element( 3220 "descendant::text:change-start", position, change_id=idx 3221 ) 3222 3223 def get_text_change_ends(self) -> list[Element]: 3224 """Return all the text change-end. Search only the tags 3225 text:change-end. 3226 Consider using : get_text_changes() 3227 3228 Return: list of Element 3229 """ 3230 return self._filtered_elements("descendant::text:change-end") 3231 3232 def get_text_change_end( 3233 self, 3234 position: int = 0, 3235 idx: str | None = None, 3236 ) -> Element | None: 3237 """Return the text change-end that matches the criteria. Search only 3238 the tags text:change-end. 3239 Consider using : get_text_change() 3240 3241 Arguments: 3242 3243 position -- int 3244 3245 idx -- str 3246 3247 Return: Element or None if not found 3248 """ 3249 return self._filtered_element( 3250 "descendant::text:change-end", position, change_id=idx 3251 ) 3252 3253 def get_text_changes(self) -> list[Element]: 3254 """Return all the text changes, either single deletion 3255 (text:change) or start of range of changes (text:change-start). 3256 3257 Return: list of Element 3258 """ 3259 request = "descendant::text:change-start | descendant::text:change" 3260 return self._filtered_elements(request) 3261 3262 def get_text_change( 3263 self, 3264 position: int = 0, 3265 idx: str | None = None, 3266 ) -> Element | None: 3267 """Return the text change that matches the criteria. Either single 3268 deletion (text:change) or start of range of changes (text:change-start). 3269 position : index of the element to retrieve if several matches, default 3270 is 0. 3271 idx : change-id of the element. 3272 3273 Arguments: 3274 3275 position -- int 3276 3277 idx -- str 3278 3279 Return: Element or None if not found 3280 """ 3281 if idx: 3282 request = ( 3283 f'descendant::text:change-start[@text:change-id="{idx}"] ' 3284 f'| descendant::text:change[@text:change-id="{idx}"]' 3285 ) 3286 return self._filtered_element(request, 0) 3287 request = "descendant::text:change-start | descendant::text:change" 3288 return self._filtered_element(request, position) 3289 3290 # Table Of Content 3291 3292 def get_tocs(self) -> list[Element]: 3293 """Return all the tables of contents. 3294 3295 Return: list of odf_toc 3296 """ 3297 return self._filtered_elements("text:table-of-content") 3298 3299 def get_toc( 3300 self, 3301 position: int = 0, 3302 content: str | None = None, 3303 ) -> Element | None: 3304 """Return the table of contents that matches the criteria. 3305 3306 Arguments: 3307 3308 position -- int 3309 3310 content -- str regex 3311 3312 Return: odf_toc or None if not found 3313 """ 3314 return self._filtered_element( 3315 "text:table-of-content", position, content=content 3316 ) 3317 3318 # Styles 3319 3320 @staticmethod 3321 def _get_style_tagname(family: str | None, is_default: bool = False) -> str: 3322 """Widely match possible tag names given the family (or not).""" 3323 if not family: 3324 tagname = "(style:default-style|*[@style:name]|draw:fill-image|draw:marker)" 3325 elif is_default: 3326 # Default style 3327 tagname = "style:default-style" 3328 else: 3329 tagname = _family_style_tagname(family) 3330 # if famattr: 3331 # # Include family default style 3332 # tagname = '(%s|style:default-style)' % tagname 3333 if family in FAMILY_ODF_STD: 3334 # Include family default style 3335 tagname = f"({tagname}|style:default-style)" 3336 return tagname 3337 3338 def get_styles(self, family: str | None = None) -> list[Element]: 3339 # Both common and default styles 3340 tagname = self._get_style_tagname(family) 3341 return self._filtered_elements(tagname, family=family) 3342 3343 def get_style( 3344 self, 3345 family: str, 3346 name_or_element: str | Element | None = None, 3347 display_name: str | None = None, 3348 ) -> Element | None: 3349 """Return the style uniquely identified by the family/name pair. If 3350 the argument is already a style object, it will return it. 3351 3352 If the name is not the internal name but the name you gave in the 3353 desktop application, use display_name instead. 3354 3355 Arguments: 3356 3357 family -- 'paragraph', 'text', 'graphic', 'table', 'list', 3358 'number' 3359 3360 name_or_element -- str or Style 3361 3362 display_name -- str 3363 3364 Return: odf_style or None if not found 3365 """ 3366 if isinstance(name_or_element, Element): 3367 name = self.get_attribute("style:name") 3368 if name is not None: 3369 return name_or_element 3370 else: 3371 raise ValueError(f"Not a odf_style ? {name_or_element!r}") 3372 style_name = name_or_element 3373 is_default = not (style_name or display_name) 3374 tagname = self._get_style_tagname(family, is_default=is_default) 3375 # famattr became None if no "style:family" attribute 3376 if family: 3377 return self._filtered_element( 3378 tagname, 3379 0, 3380 style_name=style_name, 3381 display_name=display_name, 3382 family=family, 3383 ) 3384 else: 3385 return self._filtered_element( 3386 tagname, 3387 0, 3388 draw_name=style_name or display_name, 3389 family=family, 3390 ) 3391 3392 def _filtered_element( 3393 self, 3394 query_string: str, 3395 position: int, 3396 **kwargs: Any, 3397 ) -> Element | None: 3398 results = self._filtered_elements(query_string, **kwargs) 3399 try: 3400 return results[position] 3401 except IndexError: 3402 return None 3403 3404 def _filtered_elements( 3405 self, 3406 query_string: str, 3407 content: str | None = None, 3408 url: str | None = None, 3409 svg_title: str | None = None, 3410 svg_desc: str | None = None, 3411 dc_creator: str | None = None, 3412 dc_date: datetime | None = None, 3413 **kwargs: Any, 3414 ) -> list[Element]: 3415 query = make_xpath_query(query_string, **kwargs) 3416 elements = self.get_elements(query) 3417 # Filter the elements with the regex (TODO use XPath) 3418 if content is not None: 3419 elements = [element for element in elements if element.match(content)] 3420 if url is not None: 3421 filtered = [] 3422 for element in elements: 3423 url_attr = element.get_attribute("xlink:href") 3424 if isinstance(url_attr, str) and search(url, url_attr) is not None: 3425 filtered.append(element) 3426 elements = filtered 3427 if dc_date is None: 3428 dt_dc_date = None 3429 else: 3430 dt_dc_date = DateTime.encode(dc_date) 3431 for variable, childname in [ 3432 (svg_title, "svg:title"), 3433 (svg_desc, "svg:desc"), 3434 (dc_creator, "descendant::dc:creator"), 3435 (dt_dc_date, "descendant::dc:date"), 3436 ]: 3437 if not variable: 3438 continue 3439 filtered = [] 3440 for element in elements: 3441 child = element.get_element(childname) 3442 if child and child.match(variable): 3443 filtered.append(element) 3444 elements = filtered 3445 return elements
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
316 def __init__(self, **kwargs: Any) -> None: 317 tag_or_elem = kwargs.pop("tag_or_elem", None) 318 if tag_or_elem is None: 319 # Instance for newly created object: create new lxml element and 320 # continue by subclass __init__ 321 # If the tag key word exists, make a custom element 322 self._do_init = True 323 tag = kwargs.pop("tag", self._tag) 324 self.__element = self.make_etree_element(tag) 325 else: 326 # called with an existing lxml element, sould be a result of 327 # from_tag() casting, do not execute the subclass __init__ 328 if not isinstance(tag_or_elem, _Element): 329 raise TypeError(f'"{type(tag_or_elem)}" is not an element node') 330 self._do_init = False 331 self.__element = tag_or_elem
339 @classmethod 340 def from_tag(cls, tag_or_elem: str | _Element) -> Element: 341 """Element class and subclass factory. 342 343 Turn an lxml Element or ODF string tag into an ODF XML Element 344 of the relevant class. 345 346 Arguments: 347 348 tag_or_elem -- ODF str tag or lxml.Element 349 350 Return: Element (or subclass) instance 351 """ 352 if isinstance(tag_or_elem, str): 353 # assume the argument is a prefix:name tag 354 elem = cls.make_etree_element(tag_or_elem) 355 else: 356 elem = tag_or_elem 357 klass = _class_registry.get(elem.tag, cls) 358 return klass(tag_or_elem=elem)
Element class and subclass factory.
Turn an lxml Element or ODF string tag into an ODF XML Element of the relevant class.
Arguments:
tag_or_elem -- ODF str tag or lxml.Element
Return: Element (or subclass) instance
360 @classmethod 361 def from_tag_for_clone( 362 cls: type, 363 tree_element: _Element, 364 cache: tuple | None, 365 ) -> Element: 366 tag = to_str(tree_element.tag) 367 klass = _class_registry.get(tag, cls) 368 element = klass(tag_or_elem=tree_element) 369 if cache and element._caching: 370 element._tmap = cache[0] 371 element._cmap = cache[1] 372 if len(cache) == 3: 373 element._rmap = cache[2] 374 return element
376 @staticmethod 377 def make_etree_element(tag: str) -> _Element: 378 if not isinstance(tag, str): 379 raise TypeError(f"Tag is not str: {tag!r}") 380 tag = tag.strip() 381 if not tag: 382 raise ValueError("Tag is empty") 383 if "<" not in tag: 384 # Qualified name 385 # XXX don't build the element from scratch or lxml will pollute with 386 # repeated namespace declarations 387 tag = f"<{tag}/>" 388 # XML fragment 389 root = fromstring(NAMESPACES_XML % str_to_bytes(tag)) 390 return root[0]
753 @property 754 def tag(self) -> str: 755 """Get/set the underlying xml tag with the given qualified name. 756 757 Warning: direct change of tag does not change the element class. 758 759 Arguments: 760 761 qname -- str (e.g. "text:span") 762 """ 763 return _get_prefixed_name(self.__element.tag)
Get/set the underlying xml tag with the given qualified name.
Warning: direct change of tag does not change the element class.
Arguments:
qname -- str (e.g. "text:span")
769 def elements_repeated_sequence( 770 self, 771 xpath_instance: XPath, 772 name: str, 773 ) -> list[tuple[int, int]]: 774 """Utility method for table module.""" 775 lxml_tag = _get_lxml_tag_or_name(name) 776 element = self.__element 777 sub_elements = xpath_instance(element) 778 if not isinstance(sub_elements, list): 779 raise TypeError("Bad XPath result.") 780 result: list[tuple[int, int]] = [] 781 idx = -1 782 for sub_element in sub_elements: 783 if not isinstance(sub_element, _Element): 784 continue 785 idx += 1 786 value = sub_element.get(lxml_tag) 787 if value is None: 788 result.append((idx, 1)) 789 continue 790 try: 791 int_value = int(value) 792 except ValueError: 793 int_value = 1 794 result.append((idx, max(int_value, 1))) 795 return result
Utility method for table module.
797 def get_elements(self, xpath_query: XPath | str) -> list[Element]: 798 cache: tuple | None = None 799 element = self.__element 800 if isinstance(xpath_query, str): 801 new_xpath_query = xpath_compile(xpath_query) 802 result = new_xpath_query(element) 803 else: 804 result = xpath_query(element) 805 if not isinstance(result, list): 806 raise TypeError("Bad XPath result") 807 808 if hasattr(self, "_tmap"): 809 if hasattr(self, "_rmap"): 810 cache = (self._tmap, self._cmap, self._rmap) 811 else: 812 cache = (self._tmap, self._cmap) 813 return [ 814 Element.from_tag_for_clone(e, cache) 815 for e in result 816 if isinstance(e, _Element) 817 ]
849 def get_attribute(self, name: str) -> str | bool | None: 850 """Return the attribute value as type str | bool | None.""" 851 element = self.__element 852 lxml_tag = _get_lxml_tag_or_name(name) 853 value = element.get(lxml_tag) 854 if value is None: 855 return None 856 elif value in ("true", "false"): 857 return Boolean.decode(value) 858 return str(value)
Return the attribute value as type str | bool | None.
860 def get_attribute_integer(self, name: str) -> int | None: 861 """Return either the attribute as type int, or None.""" 862 element = self.__element 863 lxml_tag = _get_lxml_tag_or_name(name) 864 value = element.get(lxml_tag) 865 if value is None: 866 return None 867 try: 868 return int(value) 869 except ValueError: 870 return None
Return either the attribute as type int, or None.
872 def get_attribute_string(self, name: str) -> str | None: 873 """Return either the attribute as type str, or None.""" 874 element = self.__element 875 lxml_tag = _get_lxml_tag_or_name(name) 876 value = element.get(lxml_tag) 877 if value is None: 878 return None 879 return str(value)
Return either the attribute as type str, or None.
881 def set_attribute( 882 self, name: str, value: bool | str | tuple[int, int, int] | None 883 ) -> None: 884 if name in ODF_COLOR_PROPERTY: 885 if isinstance(value, bool): 886 raise TypeError(f"Wrong color type {value!r}") 887 if value != "transparent": 888 value = hexa_color(value) 889 element = self.__element 890 lxml_tag = _get_lxml_tag_or_name(name) 891 if isinstance(value, bool): 892 value = Boolean.encode(value) 893 elif value is None: 894 with contextlib.suppress(KeyError): 895 del element.attrib[lxml_tag] 896 return 897 element.set(lxml_tag, str(value))
899 def set_style_attribute(self, name: str, value: Element | str) -> None: 900 """Shortcut to accept a style object as a value.""" 901 if isinstance(value, Element): 902 value = str(value.name) # type:ignore 903 return self.set_attribute(name, value)
Shortcut to accept a style object as a value.
910 @property 911 def text(self) -> str: 912 """Get / set the text content of the element.""" 913 return self.__element.text or ""
Get / set the text content of the element.
928 @property 929 def tail(self) -> str | None: 930 """Get / set the text immediately following the element.""" 931 return self.__element.tail
Get / set the text immediately following the element.
937 def search(self, pattern: str) -> int | None: 938 """Return the first position of the pattern in the text content of 939 the element, or None if not found. 940 941 Python regular expression syntax applies. 942 943 Arguments: 944 945 pattern -- str 946 947 Return: int or None 948 """ 949 match = re.search(pattern, self.text_recursive) 950 if match is None: 951 return None 952 return match.start()
Return the first position of the pattern in the text content of the element, or None if not found.
Python regular expression syntax applies.
Arguments:
pattern -- str
Return: int or None
954 def match(self, pattern: str) -> bool: 955 """return True if the pattern is found one or more times anywhere in 956 the text content of the element. 957 958 Python regular expression syntax applies. 959 960 Arguments: 961 962 pattern -- str 963 964 Return: bool 965 """ 966 return self.search(pattern) is not None
return True if the pattern is found one or more times anywhere in the text content of the element.
Python regular expression syntax applies.
Arguments:
pattern -- str
Return: bool
968 def replace(self, pattern: str, new: str | None = None) -> int: 969 """Replace the pattern with the given text, or delete if text is an 970 empty string, and return the number of replacements. By default, only 971 return the number of occurences that would be replaced. 972 973 It cannot replace patterns found across several element, like a word 974 split into two consecutive spans. 975 976 Python regular expression syntax applies. 977 978 Arguments: 979 980 pattern -- str 981 982 new -- str 983 984 Return: int 985 """ 986 if not isinstance(pattern, str): 987 # Fail properly if the pattern is an non-ascii bytestring 988 pattern = str(pattern) 989 cpattern = re.compile(pattern) 990 count = 0 991 for text in self.xpath("descendant::text()"): 992 if new is None: 993 count += len(cpattern.findall(str(text))) 994 else: 995 new_text, number = cpattern.subn(new, str(text)) 996 container = text.parent 997 if text.is_text(): # type: ignore 998 container.text = new_text # type: ignore 999 else: 1000 container.tail = new_text # type: ignore 1001 count += number 1002 return count
Replace the pattern with the given text, or delete if text is an empty string, and return the number of replacements. By default, only return the number of occurences that would be replaced.
It cannot replace patterns found across several element, like a word split into two consecutive spans.
Python regular expression syntax applies.
Arguments:
pattern -- str
new -- str
Return: int
1047 def index(self, child: Element) -> int: 1048 """Return the position of the child in this element. 1049 1050 Inspired by lxml 1051 """ 1052 return self.__element.index(child.__element)
Return the position of the child in this element.
Inspired by lxml
1054 @property 1055 def text_content(self) -> str: 1056 """Get / set the text of the embedded paragraph, including embeded 1057 annotations, cells... 1058 1059 Set create a paragraph if missing 1060 """ 1061 return "\n".join( 1062 child.text_recursive for child in self.get_elements("descendant::text:p") 1063 )
Get / set the text of the embedded paragraph, including embeded annotations, cells...
Set create a paragraph if missing
1095 def is_empty(self) -> bool: 1096 """Check if the element is empty : no text, no children, no tail. 1097 1098 Return: Boolean 1099 """ 1100 element = self.__element 1101 if element.tail is not None: 1102 return False 1103 if element.text is not None: 1104 return False 1105 if list(element.iterchildren()): 1106 return False 1107 return True
Check if the element is empty : no text, no children, no tail.
Return: Boolean
1252 def get_between( 1253 self, 1254 tag1: Element, 1255 tag2: Element, 1256 as_text: bool = False, 1257 clean: bool = True, 1258 no_header: bool = True, 1259 ) -> list | Element | str: 1260 """Returns elements between tag1 and tag2, tag1 and tag2 shall 1261 be unique and having an id attribute. 1262 (WARN: buggy if tag1/tag2 defines a malformed odf xml.) 1263 If as_text is True: returns the text content. 1264 If clean is True: suppress unwanted tags (deletions marks, ...) 1265 If no_header is True: existing text:h are changed in text:p 1266 By default: returns a list of Element, cleaned and without headers. 1267 1268 Implementation and standard retrictions: 1269 Only text:h and text:p sould be 'cut' by an insert tag, so inner parts 1270 of insert tags are: 1271 1272 - any text:h, text:p or sub tag of these 1273 1274 - some text, part of a parent text:h or text:p 1275 1276 Arguments: 1277 1278 tag1 -- Element 1279 1280 tag2 -- Element 1281 1282 as_text -- boolean 1283 1284 clean -- boolean 1285 1286 no_header -- boolean 1287 1288 Return: list of odf_paragraph or odf_header 1289 """ 1290 inner = self._get_between_base(tag1, tag2) 1291 1292 if clean: 1293 clean_tags = ( 1294 "text:change", 1295 "text:change-start", 1296 "text:change-end", 1297 "text:reference-mark", 1298 "text:reference-mark-start", 1299 "text:reference-mark-end", 1300 ) 1301 request_self = " | ".join(["self::%s" % c for c in clean_tags]) 1302 inner = [e for e in inner if not e.xpath(request_self)] 1303 request = " | ".join([f"descendant::{tag}" for tag in clean_tags]) 1304 for element in inner: 1305 to_del = element.xpath(request) 1306 for elem in to_del: 1307 if isinstance(elem, Element): 1308 element.delete(elem) 1309 if no_header: # crude replace t:h by t:p 1310 new_inner = [] 1311 for element in inner: 1312 if element.tag == "text:h": 1313 children = element.children 1314 text = element.__element.text 1315 para = Element.from_tag("text:p") 1316 para.text = text or "" 1317 for c in children: 1318 para.append(c) 1319 new_inner.append(para) 1320 else: 1321 new_inner.append(element) 1322 inner = new_inner 1323 if as_text: 1324 return "\n".join([e.get_formatted_text() for e in inner]) 1325 else: 1326 return inner
Returns elements between tag1 and tag2, tag1 and tag2 shall be unique and having an id attribute. (WARN: buggy if tag1/tag2 defines a malformed odf xml.) If as_text is True: returns the text content. If clean is True: suppress unwanted tags (deletions marks, ...) If no_header is True: existing text:h are changed in text:p By default: returns a list of Element, cleaned and without headers.
Implementation and standard retrictions: Only text:h and text:p sould be 'cut' by an insert tag, so inner parts of insert tags are:
- any text:h, text:p or sub tag of these
- some text, part of a parent text:h or text:p
Arguments:
tag1 -- Element
tag2 -- Element
as_text -- boolean
clean -- boolean
no_header -- boolean
Return: list of odf_paragraph or odf_header
1328 def insert( 1329 self, 1330 element: Element, 1331 xmlposition: int | None = None, 1332 position: int | None = None, 1333 start: bool = False, 1334 ) -> None: 1335 """Insert an element relatively to ourself. 1336 1337 Insert either using DOM vocabulary or by numeric position. 1338 If text start is True, insert the element before any existing text. 1339 1340 Position start at 0. 1341 1342 Arguments: 1343 1344 element -- Element 1345 1346 xmlposition -- FIRST_CHILD, LAST_CHILD, NEXT_SIBLING 1347 or PREV_SIBLING 1348 1349 start -- Boolean 1350 1351 position -- int 1352 """ 1353 # child_tag = element.tag 1354 current = self.__element 1355 _element = element.__element 1356 if start: 1357 text = current.text 1358 if text is not None: 1359 current.text = None 1360 tail = _element.tail 1361 if tail is None: 1362 tail = text 1363 else: 1364 tail = tail + text 1365 _element.tail = tail 1366 position = 0 1367 if position is not None: 1368 current.insert(position, _element) 1369 elif xmlposition is FIRST_CHILD: 1370 current.insert(0, _element) 1371 elif xmlposition is LAST_CHILD: 1372 current.append(_element) 1373 elif xmlposition is NEXT_SIBLING: 1374 parent = current.getparent() 1375 index = parent.index(current) # type: ignore 1376 parent.insert(index + 1, _element) # type: ignore 1377 elif xmlposition is PREV_SIBLING: 1378 parent = current.getparent() 1379 index = parent.index(current) # type: ignore 1380 parent.insert(index, _element) # type: ignore 1381 else: 1382 raise ValueError("(xml)position must be defined")
Insert an element relatively to ourself.
Insert either using DOM vocabulary or by numeric position. If text start is True, insert the element before any existing text.
Position start at 0.
Arguments:
element -- Element
xmlposition -- FIRST_CHILD, LAST_CHILD, NEXT_SIBLING
or PREV_SIBLING
start -- Boolean
position -- int
1384 def extend(self, odf_elements: Iterable[Element]) -> None: 1385 """Fast append elements at the end of ourself using extend.""" 1386 if odf_elements: 1387 current = self.__element 1388 elements = [element.__element for element in odf_elements] 1389 current.extend(elements)
Fast append elements at the end of ourself using extend.
1391 def append(self, str_or_element: str | Element) -> None: 1392 """Insert element or text in the last position.""" 1393 current = self.__element 1394 if isinstance(str_or_element, str): 1395 # Has children ? 1396 children = list(current.iterchildren()) 1397 if children: 1398 # Append to tail of the last child 1399 last_child = children[-1] 1400 text = last_child.tail 1401 text = text if text is not None else "" 1402 text += str_or_element 1403 last_child.tail = text 1404 else: 1405 # Append to text of the element 1406 text = current.text 1407 text = text if text is not None else "" 1408 text += str_or_element 1409 current.text = text 1410 elif isinstance(str_or_element, Element): 1411 current.append(str_or_element.__element) 1412 else: 1413 raise TypeError(f'Element or string expected, not "{type(str_or_element)}"')
Insert element or text in the last position.
1415 def delete(self, child: Element | None = None, keep_tail: bool = True) -> None: 1416 """Delete the given element from the XML tree. If no element is given, 1417 "self" is deleted. The XML library may allow to continue to use an 1418 element now "orphan" as long as you have a reference to it. 1419 1420 if keep_tail is True (default), the tail text is not erased. 1421 1422 Arguments: 1423 1424 child -- Element 1425 1426 keep_tail -- boolean (default to True), True for most usages. 1427 """ 1428 if child is None: 1429 parent = self.parent 1430 if parent is None: 1431 raise ValueError(f"Can't delete the root element\n{self.serialize()}") 1432 child = self 1433 else: 1434 parent = self 1435 if keep_tail and child.__element.tail is not None: 1436 current = child.__element 1437 tail = str(current.tail) 1438 current.tail = None 1439 prev = current.getprevious() 1440 if prev is not None: 1441 if prev.tail is None: 1442 prev.tail = tail 1443 else: 1444 prev.tail += tail 1445 else: 1446 if parent.__element.text is None: 1447 parent.__element.text = tail 1448 else: 1449 parent.__element.text += tail 1450 parent.__element.remove(child.__element)
Delete the given element from the XML tree. If no element is given, "self" is deleted. The XML library may allow to continue to use an element now "orphan" as long as you have a reference to it.
if keep_tail is True (default), the tail text is not erased.
Arguments:
child -- Element
keep_tail -- boolean (default to True), True for most usages.
1452 def replace_element(self, old_element: Element, new_element: Element) -> None: 1453 """Replaces in place a sub element with the element passed as second 1454 argument. 1455 1456 Warning : no clone for old element. 1457 """ 1458 current = self.__element 1459 current.replace(old_element.__element, new_element.__element)
Replaces in place a sub element with the element passed as second argument.
Warning : no clone for old element.
1461 def strip_elements( 1462 self, 1463 sub_elements: Element | Iterable[Element], 1464 ) -> Element | list: 1465 """Remove the tags of provided elements, keeping inner childs and text. 1466 1467 Return : the striped element. 1468 1469 Warning : no clone in sub_elements list. 1470 1471 Arguments: 1472 1473 sub_elements -- Element or list of Element 1474 """ 1475 if not sub_elements: 1476 return self 1477 if isinstance(sub_elements, Element): 1478 sub_elements = (sub_elements,) 1479 replacer = _get_lxml_tag("text:this-will-be-removed") 1480 for element in sub_elements: 1481 element.__element.tag = replacer 1482 strip = ("text:this-will-be-removed",) 1483 return self.strip_tags(strip=strip, default=None)
Remove the tags of provided elements, keeping inner childs and text.
Return : the striped element.
Warning : no clone in sub_elements list.
Arguments:
sub_elements -- Element or list of Element
1583 def xpath(self, xpath_query: str) -> list[Element | Text]: 1584 """Apply XPath query to the element and its subtree. Return list of 1585 Element or Text instances translated from the nodes found. 1586 """ 1587 element = self.__element 1588 xpath_instance = xpath_compile(xpath_query) 1589 elements = xpath_instance(element) 1590 result: list[Element | Text] = [] 1591 if hasattr(elements, "__iter__"): 1592 for obj in elements: # type: ignore 1593 if isinstance(obj, (_ElementStringResult, _ElementUnicodeResult)): 1594 result.append(Text(obj)) 1595 elif isinstance(obj, _Element): 1596 result.append(Element.from_tag(obj)) 1597 # else: 1598 # result.append(obj) 1599 return result
Apply XPath query to the element and its subtree. Return list of Element or Text instances translated from the nodes found.
1601 def clear(self) -> None: 1602 """Remove text, children and attributes from the element.""" 1603 self.__element.clear() 1604 if hasattr(self, "_tmap"): 1605 self._tmap: list[int] = [] 1606 if hasattr(self, "_cmap"): 1607 self._cmap: list[int] = [] 1608 if hasattr(self, "_rmap"): 1609 self._rmap: list[int] = [] 1610 if hasattr(self, "_indexes"): 1611 remember = False 1612 if "_rmap" in self._indexes: 1613 remember = True 1614 self._indexes: dict[str, dict] = {} 1615 self._indexes["_cmap"] = {} 1616 self._indexes["_tmap"] = {} 1617 if remember: 1618 self._indexes["_rmap"] = {}
Remove text, children and attributes from the element.
1620 @property 1621 def clone(self) -> Element: 1622 clone = deepcopy(self.__element) 1623 root = lxml_Element("ROOT", nsmap=ODF_NAMESPACES) 1624 root.append(clone) 1625 return self.from_tag(clone) 1626 1627 # slow data = tostring(self.__element, encoding='unicode') 1628 # return self.from_tag(data)
1635 def serialize(self, pretty: bool = False, with_ns: bool = False) -> str: 1636 """Return text serialization of XML element.""" 1637 # This copy bypasses serialization side-effects in lxml 1638 native = deepcopy(self.__element) 1639 data = tostring( 1640 native, with_tail=False, pretty_print=pretty, encoding="unicode" 1641 ) 1642 if with_ns: 1643 return data 1644 # Remove namespaces 1645 return self._strip_namespaces(data)
Return text serialization of XML element.
1649 @property 1650 def document_body(self) -> Element | None: 1651 """Return the document body : 'office:body'""" 1652 return self.get_element("//office:body/*[1]")
Return the document body : 'office:body'
1667 def get_formatted_text(self, context: dict | None = None) -> str: 1668 """This function should return a beautiful version of the text.""" 1669 return ""
This function should return a beautiful version of the text.
1671 def get_styled_elements(self, name: str = "") -> list[Element]: 1672 """Brute-force to find paragraphs, tables, etc. using the given style 1673 name (or all by default). 1674 1675 Arguments: 1676 1677 name -- str 1678 1679 Return: list 1680 """ 1681 # FIXME incomplete (and possibly inaccurate) 1682 return ( 1683 self._filtered_elements("descendant::*", text_style=name) 1684 + self._filtered_elements("descendant::*", draw_style=name) 1685 + self._filtered_elements("descendant::*", draw_text_style=name) 1686 + self._filtered_elements("descendant::*", table_style=name) 1687 + self._filtered_elements("descendant::*", page_layout=name) 1688 + self._filtered_elements("descendant::*", master_page=name) 1689 + self._filtered_elements("descendant::*", parent_style=name) 1690 )
Brute-force to find paragraphs, tables, etc. using the given style name (or all by default).
Arguments:
name -- str
Return: list
1709 @property 1710 def dc_creator(self) -> str | None: 1711 """Get dc:creator value. 1712 1713 Return: str (or None if inexistant) 1714 """ 1715 return self._get_inner_text("dc:creator")
Get dc:creator value.
Return: str (or None if inexistant)
1727 @property 1728 def dc_date(self) -> datetime | None: 1729 """Get the dc:date value. 1730 1731 Return: datetime (or None if inexistant) 1732 """ 1733 date = self._get_inner_text("dc:date") 1734 if date is None: 1735 return None 1736 return DateTime.decode(date)
Get the dc:date value.
Return: datetime (or None if inexistant)
1768 def get_sections( 1769 self, 1770 style: str | None = None, 1771 content: str | None = None, 1772 ) -> list[Element]: 1773 """Return all the sections that match the criteria. 1774 1775 Arguments: 1776 1777 style -- str 1778 1779 content -- str regex 1780 1781 Return: list of Element 1782 """ 1783 return self._filtered_elements( 1784 "text:section", text_style=style, content=content 1785 )
Return all the sections that match the criteria.
Arguments:
style -- str
content -- str regex
Return: list of Element
1787 def get_section( 1788 self, 1789 position: int = 0, 1790 content: str | None = None, 1791 ) -> Element | None: 1792 """Return the section that matches the criteria. 1793 1794 Arguments: 1795 1796 position -- int 1797 1798 content -- str regex 1799 1800 Return: Element or None if not found 1801 """ 1802 return self._filtered_element( 1803 "descendant::text:section", position, content=content 1804 )
Return the section that matches the criteria.
Arguments:
position -- int
content -- str regex
Return: Element or None if not found
1808 def get_paragraphs( 1809 self, 1810 style: str | None = None, 1811 content: str | None = None, 1812 ) -> list[Element]: 1813 """Return all the paragraphs that match the criteria. 1814 1815 Arguments: 1816 1817 style -- str 1818 1819 content -- str regex 1820 1821 Return: list of Paragraph 1822 """ 1823 return self._filtered_elements( 1824 "descendant::text:p", text_style=style, content=content 1825 )
Return all the paragraphs that match the criteria.
Arguments:
style -- str
content -- str regex
Return: list of Paragraph
1827 def get_paragraph( 1828 self, 1829 position: int = 0, 1830 content: str | None = None, 1831 ) -> Element | None: 1832 """Return the paragraph that matches the criteria. 1833 1834 Arguments: 1835 1836 position -- int 1837 1838 content -- str regex 1839 1840 Return: Paragraph or None if not found 1841 """ 1842 return self._filtered_element("descendant::text:p", position, content=content)
Return the paragraph that matches the criteria.
Arguments:
position -- int
content -- str regex
Return: Paragraph or None if not found
1846 def get_spans( 1847 self, 1848 style: str | None = None, 1849 content: str | None = None, 1850 ) -> list[Element]: 1851 """Return all the spans that match the criteria. 1852 1853 Arguments: 1854 1855 style -- str 1856 1857 content -- str regex 1858 1859 Return: list of Span 1860 """ 1861 return self._filtered_elements( 1862 "descendant::text:span", text_style=style, content=content 1863 )
Return all the spans that match the criteria.
Arguments:
style -- str
content -- str regex
Return: list of Span
1865 def get_span( 1866 self, 1867 position: int = 0, 1868 content: str | None = None, 1869 ) -> Element | None: 1870 """Return the span that matches the criteria. 1871 1872 Arguments: 1873 1874 position -- int 1875 1876 content -- str regex 1877 1878 Return: Span or None if not found 1879 """ 1880 return self._filtered_element( 1881 "descendant::text:span", position, content=content 1882 )
Return the span that matches the criteria.
Arguments:
position -- int
content -- str regex
Return: Span or None if not found
1886 def get_headers( 1887 self, 1888 style: str | None = None, 1889 outline_level: str | None = None, 1890 content: str | None = None, 1891 ) -> list[Element]: 1892 """Return all the Headers that match the criteria. 1893 1894 Arguments: 1895 1896 style -- str 1897 1898 content -- str regex 1899 1900 Return: list of Header 1901 """ 1902 return self._filtered_elements( 1903 "descendant::text:h", 1904 text_style=style, 1905 outline_level=outline_level, 1906 content=content, 1907 )
Return all the Headers that match the criteria.
Arguments:
style -- str
content -- str regex
Return: list of Header
1909 def get_header( 1910 self, 1911 position: int = 0, 1912 outline_level: str | None = None, 1913 content: str | None = None, 1914 ) -> Element | None: 1915 """Return the Header that matches the criteria. 1916 1917 Arguments: 1918 1919 position -- int 1920 1921 content -- str regex 1922 1923 Return: Header or None if not found 1924 """ 1925 return self._filtered_element( 1926 "descendant::text:h", 1927 position, 1928 outline_level=outline_level, 1929 content=content, 1930 )
Return the Header that matches the criteria.
Arguments:
position -- int
content -- str regex
Return: Header or None if not found
1934 def get_lists( 1935 self, 1936 style: str | None = None, 1937 content: str | None = None, 1938 ) -> list[Element]: 1939 """Return all the lists that match the criteria. 1940 1941 Arguments: 1942 1943 style -- str 1944 1945 content -- str regex 1946 1947 Return: list of List 1948 """ 1949 return self._filtered_elements( 1950 "descendant::text:list", text_style=style, content=content 1951 )
Return all the lists that match the criteria.
Arguments:
style -- str
content -- str regex
Return: list of List
1953 def get_list( 1954 self, 1955 position: int = 0, 1956 content: str | None = None, 1957 ) -> Element | None: 1958 """Return the list that matches the criteria. 1959 1960 Arguments: 1961 1962 position -- int 1963 1964 content -- str regex 1965 1966 Return: List or None if not found 1967 """ 1968 return self._filtered_element( 1969 "descendant::text:list", position, content=content 1970 )
Return the list that matches the criteria.
Arguments:
position -- int
content -- str regex
Return: List or None if not found
1974 def get_frames( 1975 self, 1976 presentation_class: str | None = None, 1977 style: str | None = None, 1978 title: str | None = None, 1979 description: str | None = None, 1980 content: str | None = None, 1981 ) -> list[Element]: 1982 """Return all the frames that match the criteria. 1983 1984 Arguments: 1985 1986 presentation_class -- str 1987 1988 style -- str 1989 1990 title -- str regex 1991 1992 description -- str regex 1993 1994 content -- str regex 1995 1996 Return: list of Frame 1997 """ 1998 return self._filtered_elements( 1999 "descendant::draw:frame", 2000 presentation_class=presentation_class, 2001 draw_style=style, 2002 svg_title=title, 2003 svg_desc=description, 2004 content=content, 2005 )
Return all the frames that match the criteria.
Arguments:
presentation_class -- str
style -- str
title -- str regex
description -- str regex
content -- str regex
Return: list of Frame
2007 def get_frame( 2008 self, 2009 position: int = 0, 2010 name: str | None = None, 2011 presentation_class: str | None = None, 2012 title: str | None = None, 2013 description: str | None = None, 2014 content: str | None = None, 2015 ) -> Element | None: 2016 """Return the section that matches the criteria. 2017 2018 Arguments: 2019 2020 position -- int 2021 2022 name -- str 2023 2024 presentation_class -- str 2025 2026 title -- str regex 2027 2028 description -- str regex 2029 2030 content -- str regex 2031 2032 Return: Frame or None if not found 2033 """ 2034 return self._filtered_element( 2035 "descendant::draw:frame", 2036 position, 2037 draw_name=name, 2038 presentation_class=presentation_class, 2039 svg_title=title, 2040 svg_desc=description, 2041 content=content, 2042 )
Return the section that matches the criteria.
Arguments:
position -- int
name -- str
presentation_class -- str
title -- str regex
description -- str regex
content -- str regex
Return: Frame or None if not found
2046 def get_images( 2047 self, 2048 style: str | None = None, 2049 url: str | None = None, 2050 content: str | None = None, 2051 ) -> list[Element]: 2052 """Return all the images matching the criteria. 2053 2054 Arguments: 2055 2056 style -- str 2057 2058 url -- str regex 2059 2060 content -- str regex 2061 2062 Return: list of Element 2063 """ 2064 return self._filtered_elements( 2065 "descendant::draw:image", text_style=style, url=url, content=content 2066 )
Return all the images matching the criteria.
Arguments:
style -- str
url -- str regex
content -- str regex
Return: list of Element
2068 def get_image( 2069 self, 2070 position: int = 0, 2071 name: str | None = None, 2072 url: str | None = None, 2073 content: str | None = None, 2074 ) -> Element | None: 2075 """Return the image matching the criteria. 2076 2077 Arguments: 2078 2079 position -- int 2080 2081 name -- str 2082 2083 url -- str regex 2084 2085 content -- str regex 2086 2087 Return: Element or None if not found 2088 """ 2089 # The frame is holding the name 2090 if name is not None: 2091 frame = self._filtered_element( 2092 "descendant::draw:frame", position, draw_name=name 2093 ) 2094 if frame is None: 2095 return None 2096 # The name is supposedly unique 2097 return frame.get_element("draw:image") 2098 return self._filtered_element( 2099 "descendant::draw:image", position, url=url, content=content 2100 )
Return the image matching the criteria.
Arguments:
position -- int
name -- str
url -- str regex
content -- str regex
Return: Element or None if not found
2104 def get_tables( 2105 self, 2106 style: str | None = None, 2107 content: str | None = None, 2108 ) -> list[Element]: 2109 """Return all the tables that match the criteria. 2110 2111 Arguments: 2112 2113 style -- str 2114 2115 content -- str regex 2116 2117 Return: list of Table 2118 """ 2119 return self._filtered_elements( 2120 "descendant::table:table", table_style=style, content=content 2121 )
Return all the tables that match the criteria.
Arguments:
style -- str
content -- str regex
Return: list of Table
2123 def get_table( 2124 self, 2125 position: int = 0, 2126 name: str | None = None, 2127 content: str | None = None, 2128 ) -> Element | None: 2129 """Return the table that matches the criteria. 2130 2131 Arguments: 2132 2133 position -- int 2134 2135 name -- str 2136 2137 content -- str regex 2138 2139 Return: Table or None if not found 2140 """ 2141 if name is None and content is None: 2142 result = self._filtered_element("descendant::table:table", position) 2143 else: 2144 result = self._filtered_element( 2145 "descendant::table:table", 2146 position, 2147 table_name=name, 2148 content=content, 2149 ) 2150 return result
Return the table that matches the criteria.
Arguments:
position -- int
name -- str
content -- str regex
Return: Table or None if not found
2154 def get_named_ranges(self) -> list[Element]: 2155 """Return all the tables named ranges. 2156 2157 Return: list of odf_named_range 2158 """ 2159 named_ranges = self.get_elements( 2160 "descendant::table:named-expressions/table:named-range" 2161 ) 2162 return named_ranges
Return all the tables named ranges.
Return: list of odf_named_range
2164 def get_named_range(self, name: str) -> Element | None: 2165 """Return the named range of specified name, or None if not found. 2166 2167 Arguments: 2168 2169 name -- str 2170 2171 Return: NamedRange 2172 """ 2173 named_range = self.get_elements( 2174 f'descendant::table:named-expressions/table:named-range[@table:name="{name}"][1]' 2175 ) 2176 if named_range: 2177 return named_range[0] 2178 else: 2179 return None
Return the named range of specified name, or None if not found.
Arguments:
name -- str
Return: NamedRange
2181 def append_named_range(self, named_range: Element) -> None: 2182 """Append the named range to the spreadsheet, replacing existing named 2183 range of same name if any. 2184 2185 Arguments: 2186 2187 named_range -- NamedRange 2188 """ 2189 if self.tag != "office:spreadsheet": 2190 raise ValueError(f"Element is no 'office:spreadsheet' : {self.tag}") 2191 named_expressions = self.get_element("table:named-expressions") 2192 if not named_expressions: 2193 named_expressions = Element.from_tag("table:named-expressions") 2194 self.append(named_expressions) 2195 # exists ? 2196 current = named_expressions.get_element( 2197 f'table:named-range[@table:name="{named_range.name}"][1]' # type:ignore 2198 ) 2199 if current: 2200 named_expressions.delete(current) 2201 named_expressions.append(named_range)
Append the named range to the spreadsheet, replacing existing named range of same name if any.
Arguments:
named_range -- NamedRange
2203 def delete_named_range(self, name: str) -> None: 2204 """Delete the Named Range of specified name from the spreadsheet. 2205 2206 Arguments: 2207 2208 name -- str 2209 """ 2210 if self.tag != "office:spreadsheet": 2211 raise ValueError(f"Element is no 'office:spreadsheet' : {self.tag}") 2212 named_range = self.get_named_range(name) 2213 if not named_range: 2214 return 2215 named_range.delete() 2216 named_expressions = self.get_element("table:named-expressions") 2217 if not named_expressions: 2218 return 2219 element = named_expressions.__element 2220 children = list(element.iterchildren()) 2221 if not children: 2222 self.delete(named_expressions)
Delete the Named Range of specified name from the spreadsheet.
Arguments:
name -- str
2226 def get_notes( 2227 self, 2228 note_class: str | None = None, 2229 content: str | None = None, 2230 ) -> list[Element]: 2231 """Return all the notes that match the criteria. 2232 2233 Arguments: 2234 2235 note_class -- 'footnote' or 'endnote' 2236 2237 content -- str regex 2238 2239 Return: list of Note 2240 """ 2241 return self._filtered_elements( 2242 "descendant::text:note", note_class=note_class, content=content 2243 )
Return all the notes that match the criteria.
Arguments:
note_class -- 'footnote' or 'endnote'
content -- str regex
Return: list of Note
2245 def get_note( 2246 self, 2247 position: int = 0, 2248 note_id: str | None = None, 2249 note_class: str | None = None, 2250 content: str | None = None, 2251 ) -> Element | None: 2252 """Return the note that matches the criteria. 2253 2254 Arguments: 2255 2256 position -- int 2257 2258 note_id -- str 2259 2260 note_class -- 'footnote' or 'endnote' 2261 2262 content -- str regex 2263 2264 Return: Note or None if not found 2265 """ 2266 return self._filtered_element( 2267 "descendant::text:note", 2268 position, 2269 text_id=note_id, 2270 note_class=note_class, 2271 content=content, 2272 )
Return the note that matches the criteria.
Arguments:
position -- int
note_id -- str
note_class -- 'footnote' or 'endnote'
content -- str regex
Return: Note or None if not found
2276 def get_annotations( 2277 self, 2278 creator: str | None = None, 2279 start_date: datetime | None = None, 2280 end_date: datetime | None = None, 2281 content: str | None = None, 2282 ) -> list[Element]: 2283 """Return all the annotations that match the criteria. 2284 2285 Arguments: 2286 2287 creator -- str 2288 2289 start_date -- datetime instance 2290 2291 end_date -- datetime instance 2292 2293 content -- str regex 2294 2295 Return: list of Annotation 2296 """ 2297 annotations = [] 2298 for annotation in self._filtered_elements( 2299 "descendant::office:annotation", content=content 2300 ): 2301 if creator is not None and creator != annotation.dc_creator: 2302 continue 2303 date = annotation.dc_date 2304 if date is None: 2305 continue 2306 if start_date is not None and date < start_date: 2307 continue 2308 if end_date is not None and date >= end_date: 2309 continue 2310 annotations.append(annotation) 2311 return annotations
Return all the annotations that match the criteria.
Arguments:
creator -- str
start_date -- datetime instance
end_date -- datetime instance
content -- str regex
Return: list of Annotation
2313 def get_annotation( 2314 self, 2315 position: int = 0, 2316 creator: str | None = None, 2317 start_date: datetime | None = None, 2318 end_date: datetime | None = None, 2319 content: str | None = None, 2320 name: str | None = None, 2321 ) -> Element | None: 2322 """Return the annotation that matches the criteria. 2323 2324 Arguments: 2325 2326 position -- int 2327 2328 creator -- str 2329 2330 start_date -- datetime instance 2331 2332 end_date -- datetime instance 2333 2334 content -- str regex 2335 2336 name -- str 2337 2338 Return: Annotation or None if not found 2339 """ 2340 if name is not None: 2341 return self._filtered_element( 2342 "descendant::office:annotation", 0, office_name=name 2343 ) 2344 annotations = self.get_annotations( 2345 creator=creator, start_date=start_date, end_date=end_date, content=content 2346 ) 2347 if not annotations: 2348 return None 2349 try: 2350 return annotations[position] 2351 except IndexError: 2352 return None
Return the annotation that matches the criteria.
Arguments:
position -- int
creator -- str
start_date -- datetime instance
end_date -- datetime instance
content -- str regex
name -- str
Return: Annotation or None if not found
2354 def get_annotation_ends(self) -> list[Element]: 2355 """Return all the annotation ends. 2356 2357 Return: list of Element 2358 """ 2359 return self._filtered_elements("descendant::office:annotation-end")
Return all the annotation ends.
Return: list of Element
2361 def get_annotation_end( 2362 self, 2363 position: int = 0, 2364 name: str | None = None, 2365 ) -> Element | None: 2366 """Return the annotation end that matches the criteria. 2367 2368 Arguments: 2369 2370 position -- int 2371 2372 name -- str 2373 2374 Return: Element or None if not found 2375 """ 2376 return self._filtered_element( 2377 "descendant::office:annotation-end", position, office_name=name 2378 )
Return the annotation end that matches the criteria.
Arguments:
position -- int
name -- str
Return: Element or None if not found
2382 def get_office_names(self) -> list[str]: 2383 """Return all the used office:name tags values of the element. 2384 2385 Return: list of unique str 2386 """ 2387 name_xpath_query = xpath_compile("//@office:name") 2388 response = name_xpath_query(self.__element) 2389 if not isinstance(response, list): 2390 return [] 2391 return list({str(name) for name in response if name})
Return all the used office:name tags values of the element.
Return: list of unique str
2395 def get_variable_decls(self) -> Element: 2396 """Return the container for variable declarations. Created if not 2397 found. 2398 2399 Return: Element 2400 """ 2401 variable_decls = self.get_element("//text:variable-decls") 2402 if variable_decls is None: 2403 body = self.document_body 2404 if not body: 2405 raise ValueError("Empty document.body") 2406 body.insert(Element.from_tag("text:variable-decls"), FIRST_CHILD) 2407 variable_decls = body.get_element("//text:variable-decls") 2408 2409 return variable_decls # type:ignore
Return the container for variable declarations. Created if not found.
Return: Element
2411 def get_variable_decl_list(self) -> list[Element]: 2412 """Return all the variable declarations. 2413 2414 Return: list of Element 2415 """ 2416 return self._filtered_elements("descendant::text:variable-decl")
Return all the variable declarations.
Return: list of Element
2418 def get_variable_decl(self, name: str, position: int = 0) -> Element | None: 2419 """return the variable declaration for the given name. 2420 2421 Arguments: 2422 2423 name -- str 2424 2425 position -- int 2426 2427 return: Element or none if not found 2428 """ 2429 return self._filtered_element( 2430 "descendant::text:variable-decl", position, text_name=name 2431 )
return the variable declaration for the given name.
Arguments:
name -- str
position -- int
return: Element or none if not found
2433 def get_variable_sets(self, name: str | None = None) -> list[Element]: 2434 """Return all the variable sets that match the criteria. 2435 2436 Arguments: 2437 2438 name -- str 2439 2440 Return: list of Element 2441 """ 2442 return self._filtered_elements("descendant::text:variable-set", text_name=name)
Return all the variable sets that match the criteria.
Arguments:
name -- str
Return: list of Element
2444 def get_variable_set(self, name: str, position: int = -1) -> Element | None: 2445 """Return the variable set for the given name (last one by default). 2446 2447 Arguments: 2448 2449 name -- str 2450 2451 position -- int 2452 2453 Return: Element or None if not found 2454 """ 2455 return self._filtered_element( 2456 "descendant::text:variable-set", position, text_name=name 2457 )
Return the variable set for the given name (last one by default).
Arguments:
name -- str
position -- int
Return: Element or None if not found
2459 def get_variable_set_value( 2460 self, 2461 name: str, 2462 value_type: str | None = None, 2463 ) -> bool | str | int | float | Decimal | datetime | timedelta | None: 2464 """Return the last value of the given variable name. 2465 2466 Arguments: 2467 2468 name -- str 2469 2470 value_type -- 'boolean', 'currency', 'date', 'float', 2471 'percentage', 'string', 'time' or automatic 2472 2473 Return: most appropriate Python type 2474 """ 2475 variable_set = self.get_variable_set(name) 2476 if not variable_set: 2477 return None 2478 return variable_set.get_value(value_type) # type: ignore
Return the last value of the given variable name.
Arguments:
name -- str
value_type -- 'boolean', 'currency', 'date', 'float',
'percentage', 'string', 'time' or automatic
Return: most appropriate Python type
2482 def get_user_field_decls(self) -> Element | None: 2483 """Return the container for user field declarations. Created if not 2484 found. 2485 2486 Return: Element 2487 """ 2488 user_field_decls = self.get_element("//text:user-field-decls") 2489 if user_field_decls is None: 2490 body = self.document_body 2491 if not body: 2492 raise ValueError("Empty document.body") 2493 body.insert(Element.from_tag("text:user-field-decls"), FIRST_CHILD) 2494 user_field_decls = body.get_element("//text:user-field-decls") 2495 2496 return user_field_decls
Return the container for user field declarations. Created if not found.
Return: Element
2498 def get_user_field_decl_list(self) -> list[Element]: 2499 """Return all the user field declarations. 2500 2501 Return: list of Element 2502 """ 2503 return self._filtered_elements("descendant::text:user-field-decl")
Return all the user field declarations.
Return: list of Element
2505 def get_user_field_decl(self, name: str, position: int = 0) -> Element | None: 2506 """return the user field declaration for the given name. 2507 2508 return: Element or none if not found 2509 """ 2510 return self._filtered_element( 2511 "descendant::text:user-field-decl", position, text_name=name 2512 )
return the user field declaration for the given name.
return: Element or none if not found
2514 def get_user_field_value( 2515 self, name: str, value_type: str | None = None 2516 ) -> bool | str | int | float | Decimal | datetime | timedelta | None: 2517 """Return the value of the given user field name. 2518 2519 Arguments: 2520 2521 name -- str 2522 2523 value_type -- 'boolean', 'currency', 'date', 'float', 2524 'percentage', 'string', 'time' or automatic 2525 2526 Return: most appropriate Python type 2527 """ 2528 user_field_decl = self.get_user_field_decl(name) 2529 if user_field_decl is None: 2530 return None 2531 return user_field_decl.get_value(value_type) # type: ignore
Return the value of the given user field name.
Arguments:
name -- str
value_type -- 'boolean', 'currency', 'date', 'float',
'percentage', 'string', 'time' or automatic
Return: most appropriate Python type
2536 def get_user_defined_list(self) -> list[Element]: 2537 """Return all the user defined field declarations. 2538 2539 Return: list of Element 2540 """ 2541 return self._filtered_elements("descendant::text:user-defined")
Return all the user defined field declarations.
Return: list of Element
2543 def get_user_defined(self, name: str, position: int = 0) -> Element | None: 2544 """return the user defined declaration for the given name. 2545 2546 return: Element or none if not found 2547 """ 2548 return self._filtered_element( 2549 "descendant::text:user-defined", position, text_name=name 2550 )
return the user defined declaration for the given name.
return: Element or none if not found
2552 def get_user_defined_value( 2553 self, name: str, value_type: str | None = None 2554 ) -> bool | str | int | float | Decimal | datetime | timedelta | None: 2555 """Return the value of the given user defined field name. 2556 2557 Arguments: 2558 2559 name -- str 2560 2561 value_type -- 'boolean', 'date', 'float', 2562 'string', 'time' or automatic 2563 2564 Return: most appropriate Python type 2565 """ 2566 user_defined = self.get_user_defined(name) 2567 if user_defined is None: 2568 return None 2569 return user_defined.get_value(value_type) # type: ignore
Return the value of the given user defined field name.
Arguments:
name -- str
value_type -- 'boolean', 'date', 'float',
'string', 'time' or automatic
Return: most appropriate Python type
2573 def get_draw_pages( 2574 self, 2575 style: str | None = None, 2576 content: str | None = None, 2577 ) -> list[Element]: 2578 """Return all the draw pages that match the criteria. 2579 2580 Arguments: 2581 2582 style -- str 2583 2584 content -- str regex 2585 2586 Return: list of DrawPage 2587 """ 2588 return self._filtered_elements( 2589 "descendant::draw:page", draw_style=style, content=content 2590 )
Return all the draw pages that match the criteria.
Arguments:
style -- str
content -- str regex
Return: list of DrawPage
2592 def get_draw_page( 2593 self, 2594 position: int = 0, 2595 name: str | None = None, 2596 content: str | None = None, 2597 ) -> Element | None: 2598 """Return the draw page that matches the criteria. 2599 2600 Arguments: 2601 2602 position -- int 2603 2604 name -- str 2605 2606 content -- str regex 2607 2608 Return: DrawPage or None if not found 2609 """ 2610 return self._filtered_element( 2611 "descendant::draw:page", position, draw_name=name, content=content 2612 )
Return the draw page that matches the criteria.
Arguments:
position -- int
name -- str
content -- str regex
Return: DrawPage or None if not found
2616 def get_links( 2617 self, 2618 name: str | None = None, 2619 title: str | None = None, 2620 url: str | None = None, 2621 content: str | None = None, 2622 ) -> list[Element]: 2623 """Return all the links that match the criteria. 2624 2625 Arguments: 2626 2627 name -- str 2628 2629 title -- str 2630 2631 url -- str regex 2632 2633 content -- str regex 2634 2635 Return: list of Element 2636 """ 2637 return self._filtered_elements( 2638 "descendant::text:a", 2639 office_name=name, 2640 office_title=title, 2641 url=url, 2642 content=content, 2643 )
Return all the links that match the criteria.
Arguments:
name -- str
title -- str
url -- str regex
content -- str regex
Return: list of Element
2645 def get_link( 2646 self, 2647 position: int = 0, 2648 name: str | None = None, 2649 title: str | None = None, 2650 url: str | None = None, 2651 content: str | None = None, 2652 ) -> Element | None: 2653 """Return the link that matches the criteria. 2654 2655 Arguments: 2656 2657 position -- int 2658 2659 name -- str 2660 2661 title -- str 2662 2663 url -- str regex 2664 2665 content -- str regex 2666 2667 Return: Element or None if not found 2668 """ 2669 return self._filtered_element( 2670 "descendant::text:a", 2671 position, 2672 office_name=name, 2673 office_title=title, 2674 url=url, 2675 content=content, 2676 )
Return the link that matches the criteria.
Arguments:
position -- int
name -- str
title -- str
url -- str regex
content -- str regex
Return: Element or None if not found
2680 def get_bookmarks(self) -> list[Element]: 2681 """Return all the bookmarks. 2682 2683 Return: list of Element 2684 """ 2685 return self._filtered_elements("descendant::text:bookmark")
Return all the bookmarks.
Return: list of Element
2687 def get_bookmark( 2688 self, 2689 position: int = 0, 2690 name: str | None = None, 2691 ) -> Element | None: 2692 """Return the bookmark that matches the criteria. 2693 2694 Arguments: 2695 2696 position -- int 2697 2698 name -- str 2699 2700 Return: Bookmark or None if not found 2701 """ 2702 return self._filtered_element( 2703 "descendant::text:bookmark", position, text_name=name 2704 )
Return the bookmark that matches the criteria.
Arguments:
position -- int
name -- str
Return: Bookmark or None if not found
2706 def get_bookmark_starts(self) -> list[Element]: 2707 """Return all the bookmark starts. 2708 2709 Return: list of Element 2710 """ 2711 return self._filtered_elements("descendant::text:bookmark-start")
Return all the bookmark starts.
Return: list of Element
2713 def get_bookmark_start( 2714 self, 2715 position: int = 0, 2716 name: str | None = None, 2717 ) -> Element | None: 2718 """Return the bookmark start that matches the criteria. 2719 2720 Arguments: 2721 2722 position -- int 2723 2724 name -- str 2725 2726 Return: Element or None if not found 2727 """ 2728 return self._filtered_element( 2729 "descendant::text:bookmark-start", position, text_name=name 2730 )
Return the bookmark start that matches the criteria.
Arguments:
position -- int
name -- str
Return: Element or None if not found
2732 def get_bookmark_ends(self) -> list[Element]: 2733 """Return all the bookmark ends. 2734 2735 Return: list of Element 2736 """ 2737 return self._filtered_elements("descendant::text:bookmark-end")
Return all the bookmark ends.
Return: list of Element
2739 def get_bookmark_end( 2740 self, 2741 position: int = 0, 2742 name: str | None = None, 2743 ) -> Element | None: 2744 """Return the bookmark end that matches the criteria. 2745 2746 Arguments: 2747 2748 position -- int 2749 2750 name -- str 2751 2752 Return: Element or None if not found 2753 """ 2754 return self._filtered_element( 2755 "descendant::text:bookmark-end", position, text_name=name 2756 )
Return the bookmark end that matches the criteria.
Arguments:
position -- int
name -- str
Return: Element or None if not found
2760 def get_reference_marks_single(self) -> list[Element]: 2761 """Return all the reference marks. Search only the tags 2762 text:reference-mark. 2763 Consider using : get_reference_marks() 2764 2765 Return: list of Element 2766 """ 2767 return self._filtered_elements("descendant::text:reference-mark")
Return all the reference marks. Search only the tags text:reference-mark. Consider using : get_reference_marks()
Return: list of Element
2769 def get_reference_mark_single( 2770 self, 2771 position: int = 0, 2772 name: str | None = None, 2773 ) -> Element | None: 2774 """Return the reference mark that matches the criteria. Search only the 2775 tags text:reference-mark. 2776 Consider using : get_reference_mark() 2777 2778 Arguments: 2779 2780 position -- int 2781 2782 name -- str 2783 2784 Return: Element or None if not found 2785 """ 2786 return self._filtered_element( 2787 "descendant::text:reference-mark", position, text_name=name 2788 )
Return the reference mark that matches the criteria. Search only the tags text:reference-mark. Consider using : get_reference_mark()
Arguments:
position -- int
name -- str
Return: Element or None if not found
2790 def get_reference_mark_starts(self) -> list[Element]: 2791 """Return all the reference mark starts. Search only the tags 2792 text:reference-mark-start. 2793 Consider using : get_reference_marks() 2794 2795 Return: list of Element 2796 """ 2797 return self._filtered_elements("descendant::text:reference-mark-start")
Return all the reference mark starts. Search only the tags text:reference-mark-start. Consider using : get_reference_marks()
Return: list of Element
2799 def get_reference_mark_start( 2800 self, 2801 position: int = 0, 2802 name: str | None = None, 2803 ) -> Element | None: 2804 """Return the reference mark start that matches the criteria. Search 2805 only the tags text:reference-mark-start. 2806 Consider using : get_reference_mark() 2807 2808 Arguments: 2809 2810 position -- int 2811 2812 name -- str 2813 2814 Return: Element or None if not found 2815 """ 2816 return self._filtered_element( 2817 "descendant::text:reference-mark-start", position, text_name=name 2818 )
Return the reference mark start that matches the criteria. Search only the tags text:reference-mark-start. Consider using : get_reference_mark()
Arguments:
position -- int
name -- str
Return: Element or None if not found
2820 def get_reference_mark_ends(self) -> list[Element]: 2821 """Return all the reference mark ends. Search only the tags 2822 text:reference-mark-end. 2823 Consider using : get_reference_marks() 2824 2825 Return: list of Element 2826 """ 2827 return self._filtered_elements("descendant::text:reference-mark-end")
Return all the reference mark ends. Search only the tags text:reference-mark-end. Consider using : get_reference_marks()
Return: list of Element
2829 def get_reference_mark_end( 2830 self, 2831 position: int = 0, 2832 name: str | None = None, 2833 ) -> Element | None: 2834 """Return the reference mark end that matches the criteria. Search only 2835 the tags text:reference-mark-end. 2836 Consider using : get_reference_marks() 2837 2838 Arguments: 2839 2840 position -- int 2841 2842 name -- str 2843 2844 Return: Element or None if not found 2845 """ 2846 return self._filtered_element( 2847 "descendant::text:reference-mark-end", position, text_name=name 2848 )
Return the reference mark end that matches the criteria. Search only the tags text:reference-mark-end. Consider using : get_reference_marks()
Arguments:
position -- int
name -- str
Return: Element or None if not found
2850 def get_reference_marks(self) -> list[Element]: 2851 """Return all the reference marks, either single position reference 2852 (text:reference-mark) or start of range reference 2853 (text:reference-mark-start). 2854 2855 Return: list of Element 2856 """ 2857 return self._filtered_elements( 2858 "descendant::text:reference-mark-start | descendant::text:reference-mark" 2859 )
Return all the reference marks, either single position reference (text:reference-mark) or start of range reference (text:reference-mark-start).
Return: list of Element
2861 def get_reference_mark( 2862 self, 2863 position: int = 0, 2864 name: str | None = None, 2865 ) -> Element | None: 2866 """Return the reference mark that match the criteria. Either single 2867 position reference mark (text:reference-mark) or start of range 2868 reference (text:reference-mark-start). 2869 2870 Arguments: 2871 2872 position -- int 2873 2874 name -- str 2875 2876 Return: Element or None if not found 2877 """ 2878 if name: 2879 request = ( 2880 f"descendant::text:reference-mark-start" 2881 f'[@text:name="{name}"] ' 2882 f"| descendant::text:reference-mark" 2883 f'[@text:name="{name}"]' 2884 ) 2885 return self._filtered_element(request, position=0) 2886 request = ( 2887 "descendant::text:reference-mark-start | descendant::text:reference-mark" 2888 ) 2889 return self._filtered_element(request, position)
Return the reference mark that match the criteria. Either single position reference mark (text:reference-mark) or start of range reference (text:reference-mark-start).
Arguments:
position -- int
name -- str
Return: Element or None if not found
2891 def get_references(self, name: str | None = None) -> list[Element]: 2892 """Return all the references (text:reference-ref). If name is 2893 provided, returns the references of that name. 2894 2895 Return: list of Element 2896 2897 Arguments: 2898 2899 name -- str or None 2900 """ 2901 if name is None: 2902 return self._filtered_elements("descendant::text:reference-ref") 2903 request = f'descendant::text:reference-ref[@text:ref-name="{name}"]' 2904 return self._filtered_elements(request)
Return all the references (text:reference-ref). If name is provided, returns the references of that name.
Return: list of Element
Arguments:
name -- str or None
2910 def get_draw_groups( 2911 self, 2912 title: str | None = None, 2913 description: str | None = None, 2914 content: str | None = None, 2915 ) -> list[Element]: 2916 return self._filtered_elements( 2917 "descendant::draw:g", 2918 svg_title=title, 2919 svg_desc=description, 2920 content=content, 2921 )
2923 def get_draw_group( 2924 self, 2925 position: int = 0, 2926 name: str | None = None, 2927 title: str | None = None, 2928 description: str | None = None, 2929 content: str | None = None, 2930 ) -> Element | None: 2931 return self._filtered_element( 2932 "descendant::draw:g", 2933 position, 2934 draw_name=name, 2935 svg_title=title, 2936 svg_desc=description, 2937 content=content, 2938 )
2942 def get_draw_lines( 2943 self, 2944 draw_style: str | None = None, 2945 draw_text_style: str | None = None, 2946 content: str | None = None, 2947 ) -> list[Element]: 2948 """Return all the draw lines that match the criteria. 2949 2950 Arguments: 2951 2952 draw_style -- str 2953 2954 draw_text_style -- str 2955 2956 content -- str regex 2957 2958 Return: list of odf_shape 2959 """ 2960 return self._filtered_elements( 2961 "descendant::draw:line", 2962 draw_style=draw_style, 2963 draw_text_style=draw_text_style, 2964 content=content, 2965 )
Return all the draw lines that match the criteria.
Arguments:
draw_style -- str
draw_text_style -- str
content -- str regex
Return: list of odf_shape
2967 def get_draw_line( 2968 self, 2969 position: int = 0, 2970 id: str | None = None, # noqa:A002 2971 content: str | None = None, 2972 ) -> Element | None: 2973 """Return the draw line that matches the criteria. 2974 2975 Arguments: 2976 2977 position -- int 2978 2979 id -- str 2980 2981 content -- str regex 2982 2983 Return: odf_shape or None if not found 2984 """ 2985 return self._filtered_element( 2986 "descendant::draw:line", position, draw_id=id, content=content 2987 )
Return the draw line that matches the criteria.
Arguments:
position -- int
id -- str
content -- str regex
Return: odf_shape or None if not found
2991 def get_draw_rectangles( 2992 self, 2993 draw_style: str | None = None, 2994 draw_text_style: str | None = None, 2995 content: str | None = None, 2996 ) -> list[Element]: 2997 """Return all the draw rectangles that match the criteria. 2998 2999 Arguments: 3000 3001 draw_style -- str 3002 3003 draw_text_style -- str 3004 3005 content -- str regex 3006 3007 Return: list of odf_shape 3008 """ 3009 return self._filtered_elements( 3010 "descendant::draw:rect", 3011 draw_style=draw_style, 3012 draw_text_style=draw_text_style, 3013 content=content, 3014 )
Return all the draw rectangles that match the criteria.
Arguments:
draw_style -- str
draw_text_style -- str
content -- str regex
Return: list of odf_shape
3016 def get_draw_rectangle( 3017 self, 3018 position: int = 0, 3019 id: str | None = None, # noqa:A002 3020 content: str | None = None, 3021 ) -> Element | None: 3022 """Return the draw rectangle that matches the criteria. 3023 3024 Arguments: 3025 3026 position -- int 3027 3028 id -- str 3029 3030 content -- str regex 3031 3032 Return: odf_shape or None if not found 3033 """ 3034 return self._filtered_element( 3035 "descendant::draw:rect", position, draw_id=id, content=content 3036 )
Return the draw rectangle that matches the criteria.
Arguments:
position -- int
id -- str
content -- str regex
Return: odf_shape or None if not found
3040 def get_draw_ellipses( 3041 self, 3042 draw_style: str | None = None, 3043 draw_text_style: str | None = None, 3044 content: str | None = None, 3045 ) -> list[Element]: 3046 """Return all the draw ellipses that match the criteria. 3047 3048 Arguments: 3049 3050 draw_style -- str 3051 3052 draw_text_style -- str 3053 3054 content -- str regex 3055 3056 Return: list of odf_shape 3057 """ 3058 return self._filtered_elements( 3059 "descendant::draw:ellipse", 3060 draw_style=draw_style, 3061 draw_text_style=draw_text_style, 3062 content=content, 3063 )
Return all the draw ellipses that match the criteria.
Arguments:
draw_style -- str
draw_text_style -- str
content -- str regex
Return: list of odf_shape
3065 def get_draw_ellipse( 3066 self, 3067 position: int = 0, 3068 id: str | None = None, # noqa:A002 3069 content: str | None = None, 3070 ) -> Element | None: 3071 """Return the draw ellipse that matches the criteria. 3072 3073 Arguments: 3074 3075 position -- int 3076 3077 id -- str 3078 3079 content -- str regex 3080 3081 Return: odf_shape or None if not found 3082 """ 3083 return self._filtered_element( 3084 "descendant::draw:ellipse", position, draw_id=id, content=content 3085 )
Return the draw ellipse that matches the criteria.
Arguments:
position -- int
id -- str
content -- str regex
Return: odf_shape or None if not found
3089 def get_draw_connectors( 3090 self, 3091 draw_style: str | None = None, 3092 draw_text_style: str | None = None, 3093 content: str | None = None, 3094 ) -> list[Element]: 3095 """Return all the draw connectors that match the criteria. 3096 3097 Arguments: 3098 3099 draw_style -- str 3100 3101 draw_text_style -- str 3102 3103 content -- str regex 3104 3105 Return: list of odf_shape 3106 """ 3107 return self._filtered_elements( 3108 "descendant::draw:connector", 3109 draw_style=draw_style, 3110 draw_text_style=draw_text_style, 3111 content=content, 3112 )
Return all the draw connectors that match the criteria.
Arguments:
draw_style -- str
draw_text_style -- str
content -- str regex
Return: list of odf_shape
3114 def get_draw_connector( 3115 self, 3116 position: int = 0, 3117 id: str | None = None, # noqa:A002 3118 content: str | None = None, 3119 ) -> Element | None: 3120 """Return the draw connector that matches the criteria. 3121 3122 Arguments: 3123 3124 position -- int 3125 3126 id -- str 3127 3128 content -- str regex 3129 3130 Return: odf_shape or None if not found 3131 """ 3132 return self._filtered_element( 3133 "descendant::draw:connector", position, draw_id=id, content=content 3134 )
Return the draw connector that matches the criteria.
Arguments:
position -- int
id -- str
content -- str regex
Return: odf_shape or None if not found
3136 def get_orphan_draw_connectors(self) -> list[Element]: 3137 """Return a list of connectors which don't have any shape connected 3138 to them. 3139 """ 3140 connectors = [] 3141 for connector in self.get_draw_connectors(): 3142 start_shape = connector.get_attribute("draw:start-shape") 3143 end_shape = connector.get_attribute("draw:end-shape") 3144 if start_shape is None and end_shape is None: 3145 connectors.append(connector) 3146 return connectors
Return a list of connectors which don't have any shape connected to them.
3150 def get_tracked_changes(self) -> Element | None: 3151 """Return the tracked-changes part in the text body.""" 3152 return self.get_element("//text:tracked-changes")
Return the tracked-changes part in the text body.
3154 def get_changes_ids(self) -> list[Element | Text]: 3155 """Return a list of ids that refers to a change region in the tracked 3156 changes list. 3157 """ 3158 # Insertion changes 3159 xpath_query = "descendant::text:change-start/@text:change-id" 3160 # Deletion changes 3161 xpath_query += " | descendant::text:change/@text:change-id" 3162 return self.xpath(xpath_query)
Return a list of ids that refers to a change region in the tracked changes list.
3164 def get_text_change_deletions(self) -> list[Element]: 3165 """Return all the text changes of deletion kind: the tags text:change. 3166 Consider using : get_text_changes() 3167 3168 Return: list of Element 3169 """ 3170 return self._filtered_elements("descendant::text:text:change")
Return all the text changes of deletion kind: the tags text:change. Consider using : get_text_changes()
Return: list of Element
3172 def get_text_change_deletion( 3173 self, 3174 position: int = 0, 3175 idx: str | None = None, 3176 ) -> Element | None: 3177 """Return the text change of deletion kind that matches the criteria. 3178 Search only for the tags text:change. 3179 Consider using : get_text_change() 3180 3181 Arguments: 3182 3183 position -- int 3184 3185 idx -- str 3186 3187 Return: Element or None if not found 3188 """ 3189 return self._filtered_element( 3190 "descendant::text:change", position, change_id=idx 3191 )
Return the text change of deletion kind that matches the criteria. Search only for the tags text:change. Consider using : get_text_change()
Arguments:
position -- int
idx -- str
Return: Element or None if not found
3193 def get_text_change_starts(self) -> list[Element]: 3194 """Return all the text change-start. Search only for the tags 3195 text:change-start. 3196 Consider using : get_text_changes() 3197 3198 Return: list of Element 3199 """ 3200 return self._filtered_elements("descendant::text:change-start")
Return all the text change-start. Search only for the tags text:change-start. Consider using : get_text_changes()
Return: list of Element
3202 def get_text_change_start( 3203 self, 3204 position: int = 0, 3205 idx: str | None = None, 3206 ) -> Element | None: 3207 """Return the text change-start that matches the criteria. Search 3208 only the tags text:change-start. 3209 Consider using : get_text_change() 3210 3211 Arguments: 3212 3213 position -- int 3214 3215 idx -- str 3216 3217 Return: Element or None if not found 3218 """ 3219 return self._filtered_element( 3220 "descendant::text:change-start", position, change_id=idx 3221 )
Return the text change-start that matches the criteria. Search only the tags text:change-start. Consider using : get_text_change()
Arguments:
position -- int
idx -- str
Return: Element or None if not found
3223 def get_text_change_ends(self) -> list[Element]: 3224 """Return all the text change-end. Search only the tags 3225 text:change-end. 3226 Consider using : get_text_changes() 3227 3228 Return: list of Element 3229 """ 3230 return self._filtered_elements("descendant::text:change-end")
Return all the text change-end. Search only the tags text:change-end. Consider using : get_text_changes()
Return: list of Element
3232 def get_text_change_end( 3233 self, 3234 position: int = 0, 3235 idx: str | None = None, 3236 ) -> Element | None: 3237 """Return the text change-end that matches the criteria. Search only 3238 the tags text:change-end. 3239 Consider using : get_text_change() 3240 3241 Arguments: 3242 3243 position -- int 3244 3245 idx -- str 3246 3247 Return: Element or None if not found 3248 """ 3249 return self._filtered_element( 3250 "descendant::text:change-end", position, change_id=idx 3251 )
Return the text change-end that matches the criteria. Search only the tags text:change-end. Consider using : get_text_change()
Arguments:
position -- int
idx -- str
Return: Element or None if not found
3253 def get_text_changes(self) -> list[Element]: 3254 """Return all the text changes, either single deletion 3255 (text:change) or start of range of changes (text:change-start). 3256 3257 Return: list of Element 3258 """ 3259 request = "descendant::text:change-start | descendant::text:change" 3260 return self._filtered_elements(request)
Return all the text changes, either single deletion (text:change) or start of range of changes (text:change-start).
Return: list of Element
3262 def get_text_change( 3263 self, 3264 position: int = 0, 3265 idx: str | None = None, 3266 ) -> Element | None: 3267 """Return the text change that matches the criteria. Either single 3268 deletion (text:change) or start of range of changes (text:change-start). 3269 position : index of the element to retrieve if several matches, default 3270 is 0. 3271 idx : change-id of the element. 3272 3273 Arguments: 3274 3275 position -- int 3276 3277 idx -- str 3278 3279 Return: Element or None if not found 3280 """ 3281 if idx: 3282 request = ( 3283 f'descendant::text:change-start[@text:change-id="{idx}"] ' 3284 f'| descendant::text:change[@text:change-id="{idx}"]' 3285 ) 3286 return self._filtered_element(request, 0) 3287 request = "descendant::text:change-start | descendant::text:change" 3288 return self._filtered_element(request, position)
Return the text change that matches the criteria. Either single deletion (text:change) or start of range of changes (text:change-start). position : index of the element to retrieve if several matches, default is 0. idx : change-id of the element.
Arguments:
position -- int
idx -- str
Return: Element or None if not found
3292 def get_tocs(self) -> list[Element]: 3293 """Return all the tables of contents. 3294 3295 Return: list of odf_toc 3296 """ 3297 return self._filtered_elements("text:table-of-content")
Return all the tables of contents.
Return: list of odf_toc
3299 def get_toc( 3300 self, 3301 position: int = 0, 3302 content: str | None = None, 3303 ) -> Element | None: 3304 """Return the table of contents that matches the criteria. 3305 3306 Arguments: 3307 3308 position -- int 3309 3310 content -- str regex 3311 3312 Return: odf_toc or None if not found 3313 """ 3314 return self._filtered_element( 3315 "text:table-of-content", position, content=content 3316 )
Return the table of contents that matches the criteria.
Arguments:
position -- int
content -- str regex
Return: odf_toc or None if not found
3343 def get_style( 3344 self, 3345 family: str, 3346 name_or_element: str | Element | None = None, 3347 display_name: str | None = None, 3348 ) -> Element | None: 3349 """Return the style uniquely identified by the family/name pair. If 3350 the argument is already a style object, it will return it. 3351 3352 If the name is not the internal name but the name you gave in the 3353 desktop application, use display_name instead. 3354 3355 Arguments: 3356 3357 family -- 'paragraph', 'text', 'graphic', 'table', 'list', 3358 'number' 3359 3360 name_or_element -- str or Style 3361 3362 display_name -- str 3363 3364 Return: odf_style or None if not found 3365 """ 3366 if isinstance(name_or_element, Element): 3367 name = self.get_attribute("style:name") 3368 if name is not None: 3369 return name_or_element 3370 else: 3371 raise ValueError(f"Not a odf_style ? {name_or_element!r}") 3372 style_name = name_or_element 3373 is_default = not (style_name or display_name) 3374 tagname = self._get_style_tagname(family, is_default=is_default) 3375 # famattr became None if no "style:family" attribute 3376 if family: 3377 return self._filtered_element( 3378 tagname, 3379 0, 3380 style_name=style_name, 3381 display_name=display_name, 3382 family=family, 3383 ) 3384 else: 3385 return self._filtered_element( 3386 tagname, 3387 0, 3388 draw_name=style_name or display_name, 3389 family=family, 3390 )
Return the style uniquely identified by the family/name pair. If the argument is already a style object, it will return it.
If the name is not the internal name but the name you gave in the desktop application, use display_name instead.
Arguments:
family -- 'paragraph', 'text', 'graphic', 'table', 'list',
'number'
name_or_element -- str or Style
display_name -- str
Return: odf_style or None if not found
35class ElementTyped(Element): 36 def set_value_and_type( # noqa: C901 37 self, 38 value: Any, 39 value_type: str | None = None, 40 text: str | None = None, 41 currency: str | None = None, 42 ) -> str | None: 43 # Remove possible previous value and type 44 for name in ( 45 "office:value-type", 46 "office:boolean-value", 47 "office:value", 48 "office:date-value", 49 "office:string-value", 50 "office:time-value", 51 "table:formula", 52 "office:currency", 53 "calcext:value-type", 54 "loext:value-type", 55 ): 56 with contextlib.suppress(KeyError): 57 self.del_attribute(name) 58 if isinstance(value, bytes): 59 value = bytes_to_str(value) 60 if isinstance(value_type, bytes): 61 value_type = bytes_to_str(value_type) 62 if isinstance(text, bytes): 63 text = bytes_to_str(text) 64 if isinstance(currency, bytes): 65 currency = bytes_to_str(currency) 66 if value is None: 67 self._erase_text_content() 68 return text 69 if isinstance(value, bool): 70 if value_type is None: 71 value_type = "boolean" 72 if text is None: 73 text = "true" if value else "false" 74 value = Boolean.encode(value) 75 elif isinstance(value, (int, float, Decimal)): 76 if value_type == "percentage": 77 text = "%d %%" % int(value * 100) 78 if value_type is None: 79 value_type = "float" 80 if text is None: 81 text = str(value) 82 value = str(value) 83 elif isinstance(value, datetime): 84 if value_type is None: 85 value_type = "date" 86 if text is None: 87 text = str(DateTime.encode(value)) 88 value = DateTime.encode(value) 89 elif isinstance(value, date): 90 if value_type is None: 91 value_type = "date" 92 if text is None: 93 text = str(Date.encode(value)) 94 value = Date.encode(value) 95 elif isinstance(value, str): 96 if value_type is None: 97 value_type = "string" 98 if text is None: 99 text = value 100 elif isinstance(value, timedelta): 101 if value_type is None: 102 value_type = "time" 103 if text is None: 104 text = str(Duration.encode(value)) 105 value = Duration.encode(value) 106 elif value is not None: 107 raise TypeError(f"Type unknown: '{value!r}'") 108 109 if value_type is not None: 110 self.set_attribute("office:value-type", value_type) 111 self.set_attribute("calcext:value-type", value_type) 112 if value_type == "boolean": 113 self.set_attribute("office:boolean-value", value) 114 elif value_type == "currency": 115 self.set_attribute("office:value", value) 116 self.set_attribute("office:currency", currency) 117 elif value_type == "date": 118 self.set_attribute("office:date-value", value) 119 elif value_type in ("float", "percentage"): 120 self.set_attribute("office:value", value) 121 self.set_attribute("calcext:value", value) 122 elif value_type == "string": 123 self.set_attribute("office:string-value", value) 124 elif value_type == "time": 125 self.set_attribute("office:time-value", value) 126 127 return text 128 129 def _get_typed_value( # noqa: C901 130 self, 131 value_type: str | None = None, 132 try_get_text: bool = True, 133 ) -> tuple[Any, str | None]: 134 """Return Python typed value. 135 136 Only for "with office:value-type" elements, not for meta fields.""" 137 value: Decimal | str | bool | None = None 138 if value_type is None: 139 read_value_type = self.get_attribute("office:value-type") 140 if isinstance(read_value_type, bool): 141 raise TypeError( 142 f'Wrong type for "office:value-type": {type(read_value_type)}' 143 ) 144 value_type = read_value_type 145 # value_type = to_str(value_type) 146 if value_type == "boolean": 147 value = self.get_attribute("office:boolean-value") 148 return (value, value_type) 149 if value_type in {"float", "percentage", "currency"}: 150 read_number = self.get_attribute("office:value") 151 if not isinstance(read_number, (Decimal, str)): 152 raise TypeError(f'Wrong type for "office:value": {type(read_number)}') 153 value = Decimal(read_number) 154 # Return 3 instead of 3.0 if possible 155 if int(value) == value: 156 return (int(value), value_type) 157 return (value, value_type) 158 if value_type == "date": 159 read_attribute = self.get_attribute("office:date-value") 160 if not isinstance(read_attribute, str): 161 raise TypeError( 162 f'Wrong type for "office:date-value": {type(read_attribute)}' 163 ) 164 if "T" in read_attribute: 165 return (DateTime.decode(read_attribute), value_type) 166 return (Date.decode(read_attribute), value_type) 167 if value_type == "string": 168 value = self.get_attribute("office:string-value") 169 if value is not None: 170 return (str(value), value_type) 171 if try_get_text: 172 list_value = [ 173 para.text_recursive for para in self.get_elements("text:p") 174 ] 175 if list_value: 176 return ("\n".join(list_value), value_type) 177 return (None, value_type) 178 if value_type == "time": 179 read_value = self.get_attribute("office:time-value") 180 if not isinstance(read_value, str): 181 raise TypeError( 182 f'Wrong type for "office:time-value": {type(read_value)}' 183 ) 184 time_value = Duration.decode(read_value) 185 return (time_value, value_type) 186 if value_type is None: 187 return (None, None) 188 raise ValueError(f'Unexpected value type: "{value_type}"') 189 190 def get_value( 191 self, 192 value_type: str | None = None, 193 try_get_text: bool = True, 194 get_type: bool = False, 195 ) -> Any | tuple[Any, str]: 196 """Return Python typed value. 197 198 Only for "with office:value-type" elements, not for meta fields.""" 199 if get_type: 200 return self._get_typed_value( 201 value_type=value_type, 202 try_get_text=try_get_text, 203 ) 204 return self._get_typed_value( 205 value_type=value_type, 206 try_get_text=try_get_text, 207 )[0]
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
36 def set_value_and_type( # noqa: C901 37 self, 38 value: Any, 39 value_type: str | None = None, 40 text: str | None = None, 41 currency: str | None = None, 42 ) -> str | None: 43 # Remove possible previous value and type 44 for name in ( 45 "office:value-type", 46 "office:boolean-value", 47 "office:value", 48 "office:date-value", 49 "office:string-value", 50 "office:time-value", 51 "table:formula", 52 "office:currency", 53 "calcext:value-type", 54 "loext:value-type", 55 ): 56 with contextlib.suppress(KeyError): 57 self.del_attribute(name) 58 if isinstance(value, bytes): 59 value = bytes_to_str(value) 60 if isinstance(value_type, bytes): 61 value_type = bytes_to_str(value_type) 62 if isinstance(text, bytes): 63 text = bytes_to_str(text) 64 if isinstance(currency, bytes): 65 currency = bytes_to_str(currency) 66 if value is None: 67 self._erase_text_content() 68 return text 69 if isinstance(value, bool): 70 if value_type is None: 71 value_type = "boolean" 72 if text is None: 73 text = "true" if value else "false" 74 value = Boolean.encode(value) 75 elif isinstance(value, (int, float, Decimal)): 76 if value_type == "percentage": 77 text = "%d %%" % int(value * 100) 78 if value_type is None: 79 value_type = "float" 80 if text is None: 81 text = str(value) 82 value = str(value) 83 elif isinstance(value, datetime): 84 if value_type is None: 85 value_type = "date" 86 if text is None: 87 text = str(DateTime.encode(value)) 88 value = DateTime.encode(value) 89 elif isinstance(value, date): 90 if value_type is None: 91 value_type = "date" 92 if text is None: 93 text = str(Date.encode(value)) 94 value = Date.encode(value) 95 elif isinstance(value, str): 96 if value_type is None: 97 value_type = "string" 98 if text is None: 99 text = value 100 elif isinstance(value, timedelta): 101 if value_type is None: 102 value_type = "time" 103 if text is None: 104 text = str(Duration.encode(value)) 105 value = Duration.encode(value) 106 elif value is not None: 107 raise TypeError(f"Type unknown: '{value!r}'") 108 109 if value_type is not None: 110 self.set_attribute("office:value-type", value_type) 111 self.set_attribute("calcext:value-type", value_type) 112 if value_type == "boolean": 113 self.set_attribute("office:boolean-value", value) 114 elif value_type == "currency": 115 self.set_attribute("office:value", value) 116 self.set_attribute("office:currency", currency) 117 elif value_type == "date": 118 self.set_attribute("office:date-value", value) 119 elif value_type in ("float", "percentage"): 120 self.set_attribute("office:value", value) 121 self.set_attribute("calcext:value", value) 122 elif value_type == "string": 123 self.set_attribute("office:string-value", value) 124 elif value_type == "time": 125 self.set_attribute("office:time-value", value) 126 127 return text
190 def get_value( 191 self, 192 value_type: str | None = None, 193 try_get_text: bool = True, 194 get_type: bool = False, 195 ) -> Any | tuple[Any, str]: 196 """Return Python typed value. 197 198 Only for "with office:value-type" elements, not for meta fields.""" 199 if get_type: 200 return self._get_typed_value( 201 value_type=value_type, 202 try_get_text=try_get_text, 203 ) 204 return self._get_typed_value( 205 value_type=value_type, 206 try_get_text=try_get_text, 207 )[0]
Return Python typed value.
Only for "with office:value-type" elements, not for meta fields.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
194class EllipseShape(ShapeBase): 195 """Create a ellipse shape. 196 197 Arguments: 198 199 style -- str 200 201 text_style -- str 202 203 draw_id -- str 204 205 layer -- str 206 207 position -- (str, str) 208 209 size -- (str, str) 210 211 """ 212 213 _tag = "draw:ellipse" 214 _properties: tuple[PropDef, ...] = () 215 216 def __init__( 217 self, 218 style: str | None = None, 219 text_style: str | None = None, 220 draw_id: str | None = None, 221 layer: str | None = None, 222 position: tuple | None = None, 223 size: tuple | None = None, 224 **kwargs: Any, 225 ) -> None: 226 kwargs.update( 227 { 228 "style": style, 229 "text_style": text_style, 230 "draw_id": draw_id, 231 "layer": layer, 232 "size": size, 233 "position": position, 234 } 235 ) 236 super().__init__(**kwargs)
Create a ellipse shape.
Arguments:
style -- str
text_style -- str
draw_id -- str
layer -- str
position -- (str, str)
size -- (str, str)
216 def __init__( 217 self, 218 style: str | None = None, 219 text_style: str | None = None, 220 draw_id: str | None = None, 221 layer: str | None = None, 222 position: tuple | None = None, 223 size: tuple | None = None, 224 **kwargs: Any, 225 ) -> None: 226 kwargs.update( 227 { 228 "style": style, 229 "text_style": text_style, 230 "draw_id": draw_id, 231 "layer": layer, 232 "size": size, 233 "position": position, 234 } 235 ) 236 super().__init__(**kwargs)
Inherited Members
- odfdo.shapes.ShapeBase
- get_formatted_text
- draw_id
- layer
- width
- height
- pos_x
- pos_y
- presentation_class
- style
- text_style
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
- odfdo.frame.SizeMix
- size
- odfdo.frame.PosMix
- position
168class Frame(Element, AnchorMix, PosMix, ZMix, SizeMix): 169 """ODF Frame "draw:frame" 170 171 Frames are not useful by themselves. You should consider calling 172 Frame.image_frame() or Frame.text_frame directly. 173 """ 174 175 _tag = "draw:frame" 176 _properties = ( 177 PropDef("name", "draw:name"), 178 PropDef("draw_id", "draw:id"), 179 PropDef("width", "svg:width"), 180 PropDef("height", "svg:height"), 181 PropDef("style", "draw:style-name"), 182 PropDef("pos_x", "svg:x"), 183 PropDef("pos_y", "svg:y"), 184 PropDef("presentation_class", "presentation:class"), 185 PropDef("layer", "draw:layer"), 186 PropDef("presentation_style", "presentation:style-name"), 187 ) 188 189 def __init__( # noqa: C901 190 self, 191 name: str | None = None, 192 draw_id: str | None = None, 193 style: str | None = None, 194 position: tuple | None = None, 195 size: tuple = ("1cm", "1cm"), 196 z_index: int = 0, 197 presentation_class: str | None = None, 198 anchor_type: str | None = None, 199 anchor_page: int | None = None, 200 layer: str | None = None, 201 presentation_style: str | None = None, 202 **kwargs: Any, 203 ) -> None: 204 """Create a frame element of the given size. Position is relative to the 205 context the frame is inserted in. If positioned by page, give the page 206 number and the x, y position. 207 208 Size is a (width, height) tuple and position is a (left, top) tuple; items 209 are strings including the unit, e.g. ('10cm', '15cm'). 210 211 Frames are not useful by themselves. You should consider calling: 212 Frame.image_frame() 213 or 214 Frame.text_frame() 215 216 217 Arguments: 218 219 name -- str 220 221 draw_id -- str 222 223 style -- str 224 225 position -- (str, str) 226 227 size -- (str, str) 228 229 z_index -- int (default 0) 230 231 presentation_class -- str 232 233 anchor_type -- 'page', 'frame', 'paragraph', 'char' or 'as-char' 234 235 anchor_page -- int, page number is anchor_type is 'page' 236 237 layer -- str 238 239 presentation_style -- str 240 """ 241 super().__init__(**kwargs) 242 if self._do_init: 243 self.size = size 244 self.z_index = z_index 245 if name: 246 self.name = name 247 if draw_id is not None: 248 self.draw_id = draw_id 249 if style is not None: 250 self.style = style 251 if position is not None: 252 self.position = position 253 if presentation_class is not None: 254 self.presentation_class = presentation_class 255 if anchor_type: 256 self.anchor_type = anchor_type 257 if position and not anchor_type: 258 self.anchor_type = "paragraph" 259 if anchor_page is not None: 260 self.anchor_page = anchor_page 261 if layer is not None: 262 self.layer = layer 263 if presentation_style is not None: 264 self.presentation_style = presentation_style 265 266 @classmethod 267 def image_frame( 268 cls, 269 image: Element | str, 270 text: str | None = None, 271 name: str | None = None, 272 draw_id: str | None = None, 273 style: str | None = None, 274 position: tuple | None = None, 275 size: tuple = ("1cm", "1cm"), 276 z_index: int = 0, 277 presentation_class: str | None = None, 278 anchor_type: str | None = None, 279 anchor_page: int | None = None, 280 layer: str | None = None, 281 presentation_style: str | None = None, 282 **kwargs: Any, 283 ) -> Element: 284 """Create a ready-to-use image, since image must be embedded in a 285 frame. 286 287 The optionnal text will appear above the image. 288 289 Arguments: 290 291 image -- DrawImage or str, DrawImage element or URL of the image 292 293 text -- str, text for the image 294 295 See Frame() initialization for the other arguments 296 297 Return: Frame 298 """ 299 frame = cls( 300 name=name, 301 draw_id=draw_id, 302 style=style, 303 position=position, 304 size=size, 305 z_index=z_index, 306 presentation_class=presentation_class, 307 anchor_type=anchor_type, 308 anchor_page=anchor_page, 309 layer=layer, 310 presentation_style=presentation_style, 311 **kwargs, 312 ) 313 image_element = frame.set_image(image) 314 if text: 315 image_element.text_content = text 316 return frame 317 318 @classmethod 319 def text_frame( 320 cls, 321 text_or_element: Iterable[Element] | Element | str, 322 text_style: str | None = None, 323 name: str | None = None, 324 draw_id: str | None = None, 325 style: str | None = None, 326 position: tuple | None = None, 327 size: tuple = ("1cm", "1cm"), 328 z_index: int = 0, 329 presentation_class: str | None = None, 330 anchor_type: str | None = None, 331 anchor_page: int | None = None, 332 layer: str | None = None, 333 presentation_style: str | None = None, 334 **kwargs: Any, 335 ) -> Element: 336 """Create a ready-to-use text box, since text box must be embedded in 337 a frame. 338 339 The optionnal text will appear above the image. 340 341 Arguments: 342 343 text_or_element -- str or Element, or list of them, text content 344 of the text box. 345 346 text_style -- str, name of the style for the text 347 348 See Frame() initialization for the other arguments 349 350 Return: Frame 351 """ 352 frame = cls( 353 name=name, 354 draw_id=draw_id, 355 style=style, 356 position=position, 357 size=size, 358 z_index=z_index, 359 presentation_class=presentation_class, 360 anchor_type=anchor_type, 361 anchor_page=anchor_page, 362 layer=layer, 363 presentation_style=presentation_style, 364 **kwargs, 365 ) 366 frame.set_text_box(text_or_element, text_style) 367 return frame 368 369 @property 370 def text_content(self) -> str: 371 text_box = self.get_element("draw:text-box") 372 if text_box is None: 373 return "" 374 return text_box.text_content 375 376 @text_content.setter 377 def text_content(self, text_or_element: Element | str) -> None: 378 text_box = self.get_element("draw:text-box") 379 if text_box is None: 380 text_box = Element.from_tag("draw:text-box") 381 self.append(text_box) 382 if isinstance(text_or_element, Element): 383 text_box.clear() 384 text_box.append(text_or_element) 385 else: 386 text_box.text_content = text_or_element 387 388 def get_image( 389 self, 390 position: int = 0, 391 name: str | None = None, 392 url: str | None = None, 393 content: str | None = None, 394 ) -> Element | None: 395 return self.get_element("draw:image") 396 397 def set_image(self, url_or_element: Element | str) -> Element: 398 image = self.get_image() 399 if image is None: 400 if isinstance(url_or_element, Element): 401 image = url_or_element 402 self.append(image) 403 else: 404 image = DrawImage(url_or_element) 405 self.append(image) 406 else: 407 if isinstance(url_or_element, Element): 408 image.delete() 409 image = url_or_element 410 self.append(image) 411 else: 412 image.set_url(url_or_element) # type: ignore 413 return image 414 415 def get_text_box(self) -> Element | None: 416 return self.get_element("draw:text-box") 417 418 def set_text_box( 419 self, 420 text_or_element: Iterable[Element | str] | Element | str, 421 text_style: str | None = None, 422 ) -> Element: 423 text_box = self.get_text_box() 424 if text_box is None: 425 text_box = Element.from_tag("draw:text-box") 426 self.append(text_box) 427 else: 428 text_box.clear() 429 if isinstance(text_or_element, (Element, str)): 430 text_or_element_list: Iterable[Element | str] = [text_or_element] 431 else: 432 text_or_element_list = text_or_element 433 for item in text_or_element_list: 434 if isinstance(item, str): 435 text_box.append(Paragraph(item, style=text_style)) 436 else: 437 text_box.append(item) 438 return text_box 439 440 @staticmethod 441 def _get_formatted_text_subresult(context: dict, element: Element) -> str: 442 str_list = [" "] 443 for child in element.children: 444 str_list.append(child.get_formatted_text(context)) 445 subresult = "".join(str_list) 446 subresult = subresult.replace("\n", "\n ") 447 return subresult.rstrip(" ") 448 449 def get_formatted_text( # noqa: C901 450 self, 451 context: dict | None = None, 452 ) -> str: 453 if not context: 454 context = {} 455 result = [] 456 for element in self.children: 457 tag = element.tag 458 if tag == "draw:image": 459 if context["rst_mode"]: 460 filename = element.get_attribute("xlink:href") 461 462 # Compute width and height 463 width, height = self.size 464 if width is not None: 465 width = Unit(width) 466 width = width.convert("px", DPI) 467 if height is not None: 468 height = Unit(height) 469 height = height.convert("px", DPI) 470 471 # Insert or not ? 472 if context["no_img_level"]: 473 context["img_counter"] += 1 474 ref = f"|img{context['img_counter']}|" 475 result.append(ref) 476 context["images"].append((ref, filename, (width, height))) 477 else: 478 result.append(f"\n.. image:: {filename}\n") 479 if width is not None: 480 result.append(f" :width: {width}\n") 481 if height is not None: 482 result.append(f" :height: {height}\n") 483 else: 484 result.append(f"[Image {element.get_attribute('xlink:href')}]\n") 485 elif tag == "draw:text-box": 486 result.append(self._get_formatted_text_subresult(context, element)) 487 else: 488 result.append(element.get_formatted_text(context)) 489 result.append("\n") 490 return "".join(result)
ODF Frame "draw:frame"
Frames are not useful by themselves. You should consider calling Frame.image_frame() or Frame.text_frame directly.
189 def __init__( # noqa: C901 190 self, 191 name: str | None = None, 192 draw_id: str | None = None, 193 style: str | None = None, 194 position: tuple | None = None, 195 size: tuple = ("1cm", "1cm"), 196 z_index: int = 0, 197 presentation_class: str | None = None, 198 anchor_type: str | None = None, 199 anchor_page: int | None = None, 200 layer: str | None = None, 201 presentation_style: str | None = None, 202 **kwargs: Any, 203 ) -> None: 204 """Create a frame element of the given size. Position is relative to the 205 context the frame is inserted in. If positioned by page, give the page 206 number and the x, y position. 207 208 Size is a (width, height) tuple and position is a (left, top) tuple; items 209 are strings including the unit, e.g. ('10cm', '15cm'). 210 211 Frames are not useful by themselves. You should consider calling: 212 Frame.image_frame() 213 or 214 Frame.text_frame() 215 216 217 Arguments: 218 219 name -- str 220 221 draw_id -- str 222 223 style -- str 224 225 position -- (str, str) 226 227 size -- (str, str) 228 229 z_index -- int (default 0) 230 231 presentation_class -- str 232 233 anchor_type -- 'page', 'frame', 'paragraph', 'char' or 'as-char' 234 235 anchor_page -- int, page number is anchor_type is 'page' 236 237 layer -- str 238 239 presentation_style -- str 240 """ 241 super().__init__(**kwargs) 242 if self._do_init: 243 self.size = size 244 self.z_index = z_index 245 if name: 246 self.name = name 247 if draw_id is not None: 248 self.draw_id = draw_id 249 if style is not None: 250 self.style = style 251 if position is not None: 252 self.position = position 253 if presentation_class is not None: 254 self.presentation_class = presentation_class 255 if anchor_type: 256 self.anchor_type = anchor_type 257 if position and not anchor_type: 258 self.anchor_type = "paragraph" 259 if anchor_page is not None: 260 self.anchor_page = anchor_page 261 if layer is not None: 262 self.layer = layer 263 if presentation_style is not None: 264 self.presentation_style = presentation_style
Create a frame element of the given size. Position is relative to the context the frame is inserted in. If positioned by page, give the page number and the x, y position.
Size is a (width, height) tuple and position is a (left, top) tuple; items are strings including the unit, e.g. ('10cm', '15cm').
Frames are not useful by themselves. You should consider calling: Frame.image_frame() or Frame.text_frame()
Arguments:
name -- str
draw_id -- str
style -- str
position -- (str, str)
size -- (str, str)
z_index -- int (default 0)
presentation_class -- str
anchor_type -- 'page', 'frame', 'paragraph', 'char' or 'as-char'
anchor_page -- int, page number is anchor_type is 'page'
layer -- str
presentation_style -- str
266 @classmethod 267 def image_frame( 268 cls, 269 image: Element | str, 270 text: str | None = None, 271 name: str | None = None, 272 draw_id: str | None = None, 273 style: str | None = None, 274 position: tuple | None = None, 275 size: tuple = ("1cm", "1cm"), 276 z_index: int = 0, 277 presentation_class: str | None = None, 278 anchor_type: str | None = None, 279 anchor_page: int | None = None, 280 layer: str | None = None, 281 presentation_style: str | None = None, 282 **kwargs: Any, 283 ) -> Element: 284 """Create a ready-to-use image, since image must be embedded in a 285 frame. 286 287 The optionnal text will appear above the image. 288 289 Arguments: 290 291 image -- DrawImage or str, DrawImage element or URL of the image 292 293 text -- str, text for the image 294 295 See Frame() initialization for the other arguments 296 297 Return: Frame 298 """ 299 frame = cls( 300 name=name, 301 draw_id=draw_id, 302 style=style, 303 position=position, 304 size=size, 305 z_index=z_index, 306 presentation_class=presentation_class, 307 anchor_type=anchor_type, 308 anchor_page=anchor_page, 309 layer=layer, 310 presentation_style=presentation_style, 311 **kwargs, 312 ) 313 image_element = frame.set_image(image) 314 if text: 315 image_element.text_content = text 316 return frame
Create a ready-to-use image, since image must be embedded in a frame.
The optionnal text will appear above the image.
Arguments:
image -- DrawImage or str, DrawImage element or URL of the image
text -- str, text for the image
See Frame() initialization for the other arguments
Return: Frame
318 @classmethod 319 def text_frame( 320 cls, 321 text_or_element: Iterable[Element] | Element | str, 322 text_style: str | None = None, 323 name: str | None = None, 324 draw_id: str | None = None, 325 style: str | None = None, 326 position: tuple | None = None, 327 size: tuple = ("1cm", "1cm"), 328 z_index: int = 0, 329 presentation_class: str | None = None, 330 anchor_type: str | None = None, 331 anchor_page: int | None = None, 332 layer: str | None = None, 333 presentation_style: str | None = None, 334 **kwargs: Any, 335 ) -> Element: 336 """Create a ready-to-use text box, since text box must be embedded in 337 a frame. 338 339 The optionnal text will appear above the image. 340 341 Arguments: 342 343 text_or_element -- str or Element, or list of them, text content 344 of the text box. 345 346 text_style -- str, name of the style for the text 347 348 See Frame() initialization for the other arguments 349 350 Return: Frame 351 """ 352 frame = cls( 353 name=name, 354 draw_id=draw_id, 355 style=style, 356 position=position, 357 size=size, 358 z_index=z_index, 359 presentation_class=presentation_class, 360 anchor_type=anchor_type, 361 anchor_page=anchor_page, 362 layer=layer, 363 presentation_style=presentation_style, 364 **kwargs, 365 ) 366 frame.set_text_box(text_or_element, text_style) 367 return frame
Create a ready-to-use text box, since text box must be embedded in a frame.
The optionnal text will appear above the image.
Arguments:
text_or_element -- str or Element, or list of them, text content
of the text box.
text_style -- str, name of the style for the text
See Frame() initialization for the other arguments
Return: Frame
369 @property 370 def text_content(self) -> str: 371 text_box = self.get_element("draw:text-box") 372 if text_box is None: 373 return "" 374 return text_box.text_content
Get / set the text of the embedded paragraph, including embeded annotations, cells...
Set create a paragraph if missing
388 def get_image( 389 self, 390 position: int = 0, 391 name: str | None = None, 392 url: str | None = None, 393 content: str | None = None, 394 ) -> Element | None: 395 return self.get_element("draw:image")
Return the image matching the criteria.
Arguments:
position -- int
name -- str
url -- str regex
content -- str regex
Return: Element or None if not found
397 def set_image(self, url_or_element: Element | str) -> Element: 398 image = self.get_image() 399 if image is None: 400 if isinstance(url_or_element, Element): 401 image = url_or_element 402 self.append(image) 403 else: 404 image = DrawImage(url_or_element) 405 self.append(image) 406 else: 407 if isinstance(url_or_element, Element): 408 image.delete() 409 image = url_or_element 410 self.append(image) 411 else: 412 image.set_url(url_or_element) # type: ignore 413 return image
418 def set_text_box( 419 self, 420 text_or_element: Iterable[Element | str] | Element | str, 421 text_style: str | None = None, 422 ) -> Element: 423 text_box = self.get_text_box() 424 if text_box is None: 425 text_box = Element.from_tag("draw:text-box") 426 self.append(text_box) 427 else: 428 text_box.clear() 429 if isinstance(text_or_element, (Element, str)): 430 text_or_element_list: Iterable[Element | str] = [text_or_element] 431 else: 432 text_or_element_list = text_or_element 433 for item in text_or_element_list: 434 if isinstance(item, str): 435 text_box.append(Paragraph(item, style=text_style)) 436 else: 437 text_box.append(item) 438 return text_box
449 def get_formatted_text( # noqa: C901 450 self, 451 context: dict | None = None, 452 ) -> str: 453 if not context: 454 context = {} 455 result = [] 456 for element in self.children: 457 tag = element.tag 458 if tag == "draw:image": 459 if context["rst_mode"]: 460 filename = element.get_attribute("xlink:href") 461 462 # Compute width and height 463 width, height = self.size 464 if width is not None: 465 width = Unit(width) 466 width = width.convert("px", DPI) 467 if height is not None: 468 height = Unit(height) 469 height = height.convert("px", DPI) 470 471 # Insert or not ? 472 if context["no_img_level"]: 473 context["img_counter"] += 1 474 ref = f"|img{context['img_counter']}|" 475 result.append(ref) 476 context["images"].append((ref, filename, (width, height))) 477 else: 478 result.append(f"\n.. image:: {filename}\n") 479 if width is not None: 480 result.append(f" :width: {width}\n") 481 if height is not None: 482 result.append(f" :height: {height}\n") 483 else: 484 result.append(f"[Image {element.get_attribute('xlink:href')}]\n") 485 elif tag == "draw:text-box": 486 result.append(self._get_formatted_text_subresult(context, element)) 487 else: 488 result.append(element.get_formatted_text(context)) 489 result.append("\n") 490 return "".join(result)
This function should return a beautiful version of the text.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
- odfdo.frame.AnchorMix
- ANCHOR_VALUE_CHOICE
- anchor_type
- anchor_page
- odfdo.frame.PosMix
- position
- odfdo.frame.ZMix
- z_index
- odfdo.frame.SizeMix
- size
33class Header(Paragraph): 34 """Specialised paragraph for headings "text:h".""" 35 36 _tag = "text:h" 37 _properties = ( 38 PropDef("level", "text:outline-level"), 39 PropDef("restart_numbering", "text:restart-numbering"), 40 PropDef("start_value", "text:start-value"), 41 PropDef("suppress_numbering", "text:suppress-numbering"), 42 ) 43 44 def __init__( 45 self, 46 level: int = 1, 47 text: str | None = None, 48 restart_numbering: bool = False, 49 start_value: int | None = None, 50 suppress_numbering: bool = False, 51 style: str | None = None, 52 **kwargs: Any, 53 ) -> None: 54 """Create a header element of the given style and level, containing the 55 optional given text. 56 57 Level count begins at 1. 58 59 Arguments: 60 61 level -- int 62 63 text -- str 64 65 restart_numbering -- bool 66 67 start_value -- int 68 69 style -- str 70 """ 71 super().__init__(**kwargs) 72 if self._do_init: 73 self.level = int(level) 74 if text: 75 self.text = text 76 if restart_numbering: 77 self.restart_numbering = True 78 if start_value is not None: 79 self.start_value = start_value 80 if suppress_numbering: 81 self.suppress_numbering = True 82 if style: 83 self.style = style 84 85 def get_formatted_text( 86 self, 87 context: dict | None = None, 88 simple: bool = False, 89 ) -> str: 90 if not context: 91 context = { 92 "document": None, 93 "footnotes": [], 94 "endnotes": [], 95 "annotations": [], 96 "rst_mode": False, 97 "img_counter": 0, 98 "images": [], 99 "no_img_level": 0, 100 } 101 context["no_img_level"] += 1 102 title = super().get_formatted_text(context) 103 context["no_img_level"] -= 1 104 title = title.strip() 105 title = sub(r"\s+", " ", title) 106 107 # No rst_mode ? 108 if not context["rst_mode"]: 109 return title 110 # If here in rst_mode! 111 112 # Get the level, max 5! 113 LEVEL_STYLES = "#=-~`+^°'." 114 level = int(self.level) 115 if level > len(LEVEL_STYLES): 116 raise ValueError("Too many levels of heading") 117 118 # And return the result 119 result = ["\n", title, "\n", LEVEL_STYLES[level - 1] * len(title), "\n"] 120 return "".join(result)
Specialised paragraph for headings "text:h".
44 def __init__( 45 self, 46 level: int = 1, 47 text: str | None = None, 48 restart_numbering: bool = False, 49 start_value: int | None = None, 50 suppress_numbering: bool = False, 51 style: str | None = None, 52 **kwargs: Any, 53 ) -> None: 54 """Create a header element of the given style and level, containing the 55 optional given text. 56 57 Level count begins at 1. 58 59 Arguments: 60 61 level -- int 62 63 text -- str 64 65 restart_numbering -- bool 66 67 start_value -- int 68 69 style -- str 70 """ 71 super().__init__(**kwargs) 72 if self._do_init: 73 self.level = int(level) 74 if text: 75 self.text = text 76 if restart_numbering: 77 self.restart_numbering = True 78 if start_value is not None: 79 self.start_value = start_value 80 if suppress_numbering: 81 self.suppress_numbering = True 82 if style: 83 self.style = style
Create a header element of the given style and level, containing the optional given text.
Level count begins at 1.
Arguments:
level -- int
text -- str
restart_numbering -- bool
start_value -- int
style -- str
85 def get_formatted_text( 86 self, 87 context: dict | None = None, 88 simple: bool = False, 89 ) -> str: 90 if not context: 91 context = { 92 "document": None, 93 "footnotes": [], 94 "endnotes": [], 95 "annotations": [], 96 "rst_mode": False, 97 "img_counter": 0, 98 "images": [], 99 "no_img_level": 0, 100 } 101 context["no_img_level"] += 1 102 title = super().get_formatted_text(context) 103 context["no_img_level"] -= 1 104 title = title.strip() 105 title = sub(r"\s+", " ", title) 106 107 # No rst_mode ? 108 if not context["rst_mode"]: 109 return title 110 # If here in rst_mode! 111 112 # Get the level, max 5! 113 LEVEL_STYLES = "#=-~`+^°'." 114 level = int(self.level) 115 if level > len(LEVEL_STYLES): 116 raise ValueError("Too many levels of heading") 117 118 # And return the result 119 result = ["\n", title, "\n", LEVEL_STYLES[level - 1] * len(title), "\n"] 120 return "".join(result)
This function should return a beautiful version of the text.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Paragraph
- insert_note
- insert_annotation
- insert_annotation_end
- set_reference_mark
- set_reference_mark_end
- insert_variable
- set_span
- remove_spans
- remove_span
- set_link
- remove_links
- remove_link
- insert_reference
- set_bookmark
- odfdo.paragraph_base.ParagraphBase
- append_plain_text
- style
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
40class IndexTitle(Element): 41 """The "text:index-title" element contains the title of an index. 42 43 The element has the following attributes: 44 text:name, text:protected, text:protection-key, 45 text:protection-key-digest-algorithm, text:style-name, xml:id. 46 47 The actual title is stored in a child element 48 """ 49 50 _tag = "text:index-title" 51 _properties = ( 52 PropDef("name", "text:name"), 53 PropDef("style", "text:style-name"), 54 PropDef("xml_id", "xml:id"), 55 PropDef("protected", "text:protected"), 56 PropDef("protection_key", "text:protection-key"), 57 PropDef( 58 "protection_key_digest_algorithm", "text:protection-key-digest-algorithm" 59 ), 60 ) 61 62 def __init__( 63 self, 64 name: str | None = None, 65 style: str | None = None, 66 title_text: str | None = None, 67 title_text_style: str | None = None, 68 xml_id: str | None = None, 69 **kwargs: Any, 70 ) -> None: 71 super().__init__(**kwargs) 72 if self._do_init: 73 if name: 74 self.name = name 75 if style: 76 self.style = style 77 if xml_id: 78 self.xml_id = xml_id 79 if title_text: 80 self.set_title_text(title_text, title_text_style) 81 82 def set_title_text( 83 self, 84 title_text: str, 85 title_text_style: str | None = None, 86 ) -> None: 87 title = Paragraph(title_text, style=title_text_style) 88 self.append(title)
The "text:index-title" element contains the title of an index.
The element has the following attributes: text:name, text:protected, text:protection-key, text:protection-key-digest-algorithm, text:style-name, xml:id.
The actual title is stored in a child element
62 def __init__( 63 self, 64 name: str | None = None, 65 style: str | None = None, 66 title_text: str | None = None, 67 title_text_style: str | None = None, 68 xml_id: str | None = None, 69 **kwargs: Any, 70 ) -> None: 71 super().__init__(**kwargs) 72 if self._do_init: 73 if name: 74 self.name = name 75 if style: 76 self.style = style 77 if xml_id: 78 self.xml_id = xml_id 79 if title_text: 80 self.set_title_text(title_text, title_text_style)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
447class IndexTitleTemplate(Element): 448 """ODF "text:index-title-template" 449 450 Arguments: 451 452 style -- str 453 """ 454 455 _tag = "text:index-title-template" 456 _properties = (PropDef("style", "text:style-name"),) 457 458 def __init__(self, style: str | None = None, **kwargs: Any) -> None: 459 super().__init__(**kwargs) 460 if self._do_init and style: 461 self.style = style
ODF "text:index-title-template"
Arguments:
style -- str
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
223class LineBreak(Element): 224 """This element represents a line break "text:line-break" """ 225 226 _tag = "text:line-break" 227 228 def __init__(self, **kwargs: Any) -> None: 229 super().__init__(**kwargs)
This element represents a line break "text:line-break"
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
89class LineShape(ShapeBase): 90 """Create a line shape. 91 92 Arguments: 93 94 style -- str 95 96 text_style -- str 97 98 draw_id -- str 99 100 layer -- str 101 102 p1 -- (str, str) 103 104 p2 -- (str, str) 105 """ 106 107 _tag = "draw:line" 108 _properties: tuple[PropDef, ...] = ( 109 PropDef("x1", "svg:x1"), 110 PropDef("y1", "svg:y1"), 111 PropDef("x2", "svg:x2"), 112 PropDef("y2", "svg:y2"), 113 ) 114 115 def __init__( 116 self, 117 style: str | None = None, 118 text_style: str | None = None, 119 draw_id: str | None = None, 120 layer: str | None = None, 121 p1: tuple | None = None, 122 p2: tuple | None = None, 123 **kwargs: Any, 124 ) -> None: 125 kwargs.update( 126 { 127 "style": style, 128 "text_style": text_style, 129 "draw_id": draw_id, 130 "layer": layer, 131 } 132 ) 133 super().__init__(**kwargs) 134 if self._do_init: 135 if p1: 136 self.x1 = p1[0] 137 self.y1 = p1[1] 138 if p2: 139 self.x2 = p2[0] 140 self.y2 = p2[1]
Create a line shape.
Arguments:
style -- str
text_style -- str
draw_id -- str
layer -- str
p1 -- (str, str)
p2 -- (str, str)
115 def __init__( 116 self, 117 style: str | None = None, 118 text_style: str | None = None, 119 draw_id: str | None = None, 120 layer: str | None = None, 121 p1: tuple | None = None, 122 p2: tuple | None = None, 123 **kwargs: Any, 124 ) -> None: 125 kwargs.update( 126 { 127 "style": style, 128 "text_style": text_style, 129 "draw_id": draw_id, 130 "layer": layer, 131 } 132 ) 133 super().__init__(**kwargs) 134 if self._do_init: 135 if p1: 136 self.x1 = p1[0] 137 self.y1 = p1[1] 138 if p2: 139 self.x2 = p2[0] 140 self.y2 = p2[1]
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- odfdo.shapes.ShapeBase
- get_formatted_text
- draw_id
- layer
- width
- height
- pos_x
- pos_y
- presentation_class
- style
- text_style
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
- odfdo.frame.SizeMix
- size
- odfdo.frame.PosMix
- position
33class Link(ParagraphBase): 34 """Link class, "text:a" ODF element.""" 35 36 _tag = "text:a" 37 _properties: tuple[PropDef, ...] = ( 38 PropDef("url", "xlink:href"), 39 PropDef("name", "office:name"), 40 PropDef("title", "office:title"), 41 PropDef("target_frame", "office:target-frame-name"), 42 PropDef("show", "xlink:show"), 43 PropDef("visited_style", "text:visited-style-name"), 44 PropDef("style", "text:style-name"), 45 ) 46 47 def __init__( 48 self, 49 url: str | None = "", 50 name: str | None = None, 51 title: str | None = None, 52 text: str | None = None, 53 target_frame: str | None = None, 54 style: str | None = None, 55 visited_style: str | None = None, 56 **kwargs: Any, 57 ) -> None: 58 """ 59 Arguments: 60 61 url -- str 62 63 name -- str 64 65 title -- str 66 67 text -- str 68 69 target_frame -- '_self', '_blank', '_parent', '_top' 70 71 style -- str 72 73 visited_style -- str 74 """ 75 super().__init__(**kwargs) 76 if self._do_init: 77 self.url = url 78 if name is not None: 79 self.name = name 80 if title is not None: 81 self.title = title 82 if text is not None: 83 self.text = text 84 if target_frame is not None: 85 self.target_frame = target_frame 86 # show can be: 'new' or 'replace'" 87 if target_frame == "_blank": 88 self.show = "new" 89 else: 90 self.show = "replace" 91 if style is not None: 92 self.style = style 93 if visited_style is not None: 94 self.visited_style = visited_style 95 96 def __repr__(self) -> str: 97 return f"<{self.__class__.__name__} tag={self.tag} link={self.url}>" 98 99 def __str__(self) -> str: 100 if self.name: 101 return f"[{self.name}]({self.url})" 102 return f"({self.url})"
Link class, "text:a" ODF element.
47 def __init__( 48 self, 49 url: str | None = "", 50 name: str | None = None, 51 title: str | None = None, 52 text: str | None = None, 53 target_frame: str | None = None, 54 style: str | None = None, 55 visited_style: str | None = None, 56 **kwargs: Any, 57 ) -> None: 58 """ 59 Arguments: 60 61 url -- str 62 63 name -- str 64 65 title -- str 66 67 text -- str 68 69 target_frame -- '_self', '_blank', '_parent', '_top' 70 71 style -- str 72 73 visited_style -- str 74 """ 75 super().__init__(**kwargs) 76 if self._do_init: 77 self.url = url 78 if name is not None: 79 self.name = name 80 if title is not None: 81 self.title = title 82 if text is not None: 83 self.text = text 84 if target_frame is not None: 85 self.target_frame = target_frame 86 # show can be: 'new' or 'replace'" 87 if target_frame == "_blank": 88 self.show = "new" 89 else: 90 self.show = "replace" 91 if style is not None: 92 self.style = style 93 if visited_style is not None: 94 self.visited_style = visited_style
Arguments:
url -- str
name -- str
title -- str
text -- str
target_frame -- '_self', '_blank', '_parent', '_top'
style -- str
visited_style -- str
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- odfdo.paragraph_base.ParagraphBase
- get_formatted_text
- append_plain_text
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
68class List(Element): 69 """ODF List "text:list".""" 70 71 _tag = "text:list" 72 _properties = (PropDef("style", "text:style-name"),) 73 74 def __init__( 75 self, 76 list_content: str | Element | Iterable[str | Element] | None = None, 77 style: str | None = None, 78 **kwargs: Any, 79 ) -> None: 80 """Create a list element, optionaly loading the list by a list of 81 item (str or elements). 82 83 The list_content argument is just a shortcut for the most common case. 84 To create more complex lists, first create an empty list, and fill it 85 afterwards. 86 87 Arguments: 88 89 list_content -- str or Element, or a list of str or Element 90 91 style -- str 92 """ 93 super().__init__(**kwargs) 94 if self._do_init: 95 if list_content: 96 if isinstance(list_content, (Element, str)): 97 self.append(ListItem(list_content)) 98 elif hasattr(list_content, "__iter__"): 99 for item in list_content: 100 self.append(ListItem(item)) 101 if style is not None: 102 self.style = style 103 104 def get_items(self, content: str | None = None) -> list[Element]: 105 """Return all the list items that match the criteria. 106 107 Arguments: 108 109 style -- str 110 111 content -- str regex 112 113 Return: list of Element 114 """ 115 return self._filtered_elements("text:list-item", content=content) 116 117 def get_item( 118 self, 119 position: int = 0, 120 content: str | None = None, 121 ) -> Element | None: 122 """Return the list item that matches the criteria. In nested lists, 123 return the list item that really contains that content. 124 125 Arguments: 126 127 position -- int 128 129 content -- str regex 130 131 Return: Element or None if not found 132 """ 133 # Custom implementation because of nested lists 134 if content: 135 # Don't search recursively but on the very own paragraph(s) of 136 # each list item 137 for paragraph in self.get_elements("descendant::text:p"): 138 if paragraph.match(content): 139 return paragraph.get_element("parent::text:list-item") 140 return None 141 return self._filtered_element("text:list-item", position) 142 143 def set_list_header( 144 self, 145 text_or_element: str | Element | Iterable[str | Element], 146 ) -> None: 147 if isinstance(text_or_element, (str, Element)): 148 actual_list: list[str | Element] | tuple = [text_or_element] 149 elif isinstance(text_or_element, (list, tuple)): 150 actual_list = text_or_element 151 else: 152 raise TypeError 153 # Remove existing header 154 for element in self.get_elements("text:p"): 155 self.delete(element) 156 for paragraph in reversed(actual_list): 157 if isinstance(paragraph, str): 158 paragraph = Paragraph(paragraph) 159 self.insert(paragraph, FIRST_CHILD) 160 161 def insert_item( 162 self, 163 item: ListItem | str | Element | None, 164 position: int | None = None, 165 before: Element | None = None, 166 after: Element | None = None, 167 ) -> None: 168 if not isinstance(item, ListItem): 169 item = ListItem(item) 170 if before is not None: 171 before.insert(item, xmlposition=PREV_SIBLING) 172 elif after is not None: 173 after.insert(item, xmlposition=NEXT_SIBLING) 174 elif position is not None: 175 self.insert(item, position=position) 176 else: 177 raise ValueError("Position must be defined") 178 179 def append_item( 180 self, 181 item: ListItem | str | Element | None, 182 ) -> None: 183 if not isinstance(item, ListItem): 184 item = ListItem(item) 185 self.append(item) 186 187 def get_formatted_text(self, context: dict | None = None) -> str: 188 if context is None: 189 context = {} 190 rst_mode = context["rst_mode"] 191 result = [] 192 if rst_mode: 193 result.append("\n") 194 for list_item in self.get_elements("text:list-item"): 195 textbuf = [] 196 for child in list_item.children: 197 text = child.get_formatted_text(context) 198 tag = child.tag 199 if tag == "text:h": 200 # A title in a list is a bug 201 return text 202 if tag == "text:list" and not text.lstrip().startswith("-"): 203 # If the list didn't indent, don't either 204 # (inner title) 205 return text 206 textbuf.append(text) 207 text_sum = "".join(textbuf) 208 text_sum = text_sum.strip("\n") 209 # Indent the text 210 text_sum = text_sum.replace("\n", "\n ") 211 text_sum = f"- {text_sum}\n" 212 result.append(text_sum) 213 if rst_mode: 214 result.append("\n") 215 return "".join(result)
ODF List "text:list".
74 def __init__( 75 self, 76 list_content: str | Element | Iterable[str | Element] | None = None, 77 style: str | None = None, 78 **kwargs: Any, 79 ) -> None: 80 """Create a list element, optionaly loading the list by a list of 81 item (str or elements). 82 83 The list_content argument is just a shortcut for the most common case. 84 To create more complex lists, first create an empty list, and fill it 85 afterwards. 86 87 Arguments: 88 89 list_content -- str or Element, or a list of str or Element 90 91 style -- str 92 """ 93 super().__init__(**kwargs) 94 if self._do_init: 95 if list_content: 96 if isinstance(list_content, (Element, str)): 97 self.append(ListItem(list_content)) 98 elif hasattr(list_content, "__iter__"): 99 for item in list_content: 100 self.append(ListItem(item)) 101 if style is not None: 102 self.style = style
Create a list element, optionaly loading the list by a list of item (str or elements).
The list_content argument is just a shortcut for the most common case. To create more complex lists, first create an empty list, and fill it afterwards.
Arguments:
list_content -- str or Element, or a list of str or Element
style -- str
104 def get_items(self, content: str | None = None) -> list[Element]: 105 """Return all the list items that match the criteria. 106 107 Arguments: 108 109 style -- str 110 111 content -- str regex 112 113 Return: list of Element 114 """ 115 return self._filtered_elements("text:list-item", content=content)
Return all the list items that match the criteria.
Arguments:
style -- str
content -- str regex
Return: list of Element
117 def get_item( 118 self, 119 position: int = 0, 120 content: str | None = None, 121 ) -> Element | None: 122 """Return the list item that matches the criteria. In nested lists, 123 return the list item that really contains that content. 124 125 Arguments: 126 127 position -- int 128 129 content -- str regex 130 131 Return: Element or None if not found 132 """ 133 # Custom implementation because of nested lists 134 if content: 135 # Don't search recursively but on the very own paragraph(s) of 136 # each list item 137 for paragraph in self.get_elements("descendant::text:p"): 138 if paragraph.match(content): 139 return paragraph.get_element("parent::text:list-item") 140 return None 141 return self._filtered_element("text:list-item", position)
Return the list item that matches the criteria. In nested lists, return the list item that really contains that content.
Arguments:
position -- int
content -- str regex
Return: Element or None if not found
143 def set_list_header( 144 self, 145 text_or_element: str | Element | Iterable[str | Element], 146 ) -> None: 147 if isinstance(text_or_element, (str, Element)): 148 actual_list: list[str | Element] | tuple = [text_or_element] 149 elif isinstance(text_or_element, (list, tuple)): 150 actual_list = text_or_element 151 else: 152 raise TypeError 153 # Remove existing header 154 for element in self.get_elements("text:p"): 155 self.delete(element) 156 for paragraph in reversed(actual_list): 157 if isinstance(paragraph, str): 158 paragraph = Paragraph(paragraph) 159 self.insert(paragraph, FIRST_CHILD)
161 def insert_item( 162 self, 163 item: ListItem | str | Element | None, 164 position: int | None = None, 165 before: Element | None = None, 166 after: Element | None = None, 167 ) -> None: 168 if not isinstance(item, ListItem): 169 item = ListItem(item) 170 if before is not None: 171 before.insert(item, xmlposition=PREV_SIBLING) 172 elif after is not None: 173 after.insert(item, xmlposition=NEXT_SIBLING) 174 elif position is not None: 175 self.insert(item, position=position) 176 else: 177 raise ValueError("Position must be defined")
187 def get_formatted_text(self, context: dict | None = None) -> str: 188 if context is None: 189 context = {} 190 rst_mode = context["rst_mode"] 191 result = [] 192 if rst_mode: 193 result.append("\n") 194 for list_item in self.get_elements("text:list-item"): 195 textbuf = [] 196 for child in list_item.children: 197 text = child.get_formatted_text(context) 198 tag = child.tag 199 if tag == "text:h": 200 # A title in a list is a bug 201 return text 202 if tag == "text:list" and not text.lstrip().startswith("-"): 203 # If the list didn't indent, don't either 204 # (inner title) 205 return text 206 textbuf.append(text) 207 text_sum = "".join(textbuf) 208 text_sum = text_sum.strip("\n") 209 # Indent the text 210 text_sum = text_sum.replace("\n", "\n ") 211 text_sum = f"- {text_sum}\n" 212 result.append(text_sum) 213 if rst_mode: 214 result.append("\n") 215 return "".join(result)
This function should return a beautiful version of the text.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
41class ListItem(Element): 42 """ODF element "text:list-item", item of a List.""" 43 44 _tag = "text:list-item" 45 46 def __init__( 47 self, 48 text_or_element: str | Element | None = None, 49 **kwargs: Any, 50 ) -> None: 51 """Create a list item element, optionaly passing at creation time a 52 string or Element as content. 53 54 Arguments: 55 56 text_or_element -- str or ODF Element 57 """ 58 super().__init__(**kwargs) 59 if self._do_init: 60 if isinstance(text_or_element, str): 61 self.text_content = text_or_element 62 elif isinstance(text_or_element, Element): 63 self.append(text_or_element) 64 elif text_or_element is not None: 65 raise TypeError("Expected str or Element")
ODF element "text:list-item", item of a List.
46 def __init__( 47 self, 48 text_or_element: str | Element | None = None, 49 **kwargs: Any, 50 ) -> None: 51 """Create a list item element, optionaly passing at creation time a 52 string or Element as content. 53 54 Arguments: 55 56 text_or_element -- str or ODF Element 57 """ 58 super().__init__(**kwargs) 59 if self._do_init: 60 if isinstance(text_or_element, str): 61 self.text_content = text_or_element 62 elif isinstance(text_or_element, Element): 63 self.append(text_or_element) 64 elif text_or_element is not None: 65 raise TypeError("Expected str or Element")
Create a list item element, optionaly passing at creation time a string or Element as content.
Arguments:
text_or_element -- str or ODF Element
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
31class Manifest(XmlPart): 32 def get_paths(self) -> list[Element | Text]: 33 """Return the list of full paths in the manifest. 34 35 Return: list of str 36 """ 37 xpath_query = "//manifest:file-entry/attribute::manifest:full-path" 38 return self.xpath(xpath_query) 39 40 def _file_entry(self, full_path: str) -> Element: 41 xpath_query = ( 42 f'//manifest:file-entry[attribute::manifest:full-path="{full_path}"]' 43 ) 44 result = self.xpath(xpath_query) 45 if not result: 46 raise KeyError(f"Path not found: '{full_path}'") 47 return result[0] # type: ignore 48 49 def get_path_medias(self) -> list[tuple]: 50 """Return the list of (full_path, media_type) pairs in the manifest. 51 52 Return: list of str tuples 53 """ 54 xpath_query = "//manifest:file-entry" 55 result = [] 56 for file_entry in self.xpath(xpath_query): 57 if not isinstance(file_entry, Element): 58 continue 59 result.append( 60 ( 61 file_entry.get_attribute_string("manifest:full-path"), 62 file_entry.get_attribute_string("manifest:media-type"), 63 ) 64 ) 65 return result 66 67 def get_media_type(self, full_path: str) -> str | None: 68 """Get the media type of an existing path. 69 70 Return: str 71 """ 72 xpath_query = ( 73 f'//manifest:file-entry[attribute::manifest:full-path="{full_path}"]' 74 "/attribute::manifest:media-type" 75 ) 76 result = self.xpath(xpath_query) 77 if not result: 78 return None 79 return str(result[0]) 80 81 def set_media_type(self, full_path: str, media_type: str) -> None: 82 """Set the media type of an existing path. 83 84 Arguments: 85 86 full_path -- str 87 88 media_type -- str 89 """ 90 file_entry = self._file_entry(full_path) 91 file_entry.set_attribute("manifest:media-type", media_type) 92 93 @staticmethod 94 def make_file_entry(full_path: str, media_type: str) -> Element: 95 tag = ( 96 f"<manifest:file-entry " 97 f'manifest:media-type="{media_type}" ' 98 f'manifest:full-path="{full_path}"/>' 99 ) 100 return Element.from_tag(tag) 101 102 def add_full_path(self, full_path: str, media_type: str = "") -> None: 103 # Existing? 104 existing = self.get_media_type(full_path) 105 if existing is not None: 106 self.set_media_type(full_path, media_type) 107 root = self.root 108 root.append(self.make_file_entry(full_path, media_type)) 109 110 def del_full_path(self, full_path: str) -> None: 111 file_entry = self._file_entry(full_path) 112 self.root.delete(file_entry)
Representation of an XML part.
Abstraction of the XML library behind.
32 def get_paths(self) -> list[Element | Text]: 33 """Return the list of full paths in the manifest. 34 35 Return: list of str 36 """ 37 xpath_query = "//manifest:file-entry/attribute::manifest:full-path" 38 return self.xpath(xpath_query)
Return the list of full paths in the manifest.
Return: list of str
49 def get_path_medias(self) -> list[tuple]: 50 """Return the list of (full_path, media_type) pairs in the manifest. 51 52 Return: list of str tuples 53 """ 54 xpath_query = "//manifest:file-entry" 55 result = [] 56 for file_entry in self.xpath(xpath_query): 57 if not isinstance(file_entry, Element): 58 continue 59 result.append( 60 ( 61 file_entry.get_attribute_string("manifest:full-path"), 62 file_entry.get_attribute_string("manifest:media-type"), 63 ) 64 ) 65 return result
Return the list of (full_path, media_type) pairs in the manifest.
Return: list of str tuples
67 def get_media_type(self, full_path: str) -> str | None: 68 """Get the media type of an existing path. 69 70 Return: str 71 """ 72 xpath_query = ( 73 f'//manifest:file-entry[attribute::manifest:full-path="{full_path}"]' 74 "/attribute::manifest:media-type" 75 ) 76 result = self.xpath(xpath_query) 77 if not result: 78 return None 79 return str(result[0])
Get the media type of an existing path.
Return: str
81 def set_media_type(self, full_path: str, media_type: str) -> None: 82 """Set the media type of an existing path. 83 84 Arguments: 85 86 full_path -- str 87 88 media_type -- str 89 """ 90 file_entry = self._file_entry(full_path) 91 file_entry.set_attribute("manifest:media-type", media_type)
Set the media type of an existing path.
Arguments:
full_path -- str
media_type -- str
Inherited Members
43class Meta(XmlPart): 44 def __init__(self, *args: Any, **kwargs: Any) -> None: 45 super().__init__(*args, **kwargs) 46 self._generator_modified: bool = False 47 48 def get_meta_body(self) -> Element: 49 return self.get_element("//office:meta") 50 51 def get_title(self) -> str | None: 52 """Get the title of the document. 53 54 This is not the first heading but the title metadata. 55 56 Return: str (or None if inexistant) 57 """ 58 element = self.get_element("//dc:title") 59 if element is None: 60 return None 61 return element.text 62 63 def set_title(self, title: str) -> None: 64 """Set the title of the document. 65 66 This is not the first heading but the title metadata. 67 68 Arguments: 69 70 title -- str 71 """ 72 element = self.get_element("//dc:title") 73 if element is None: 74 element = Element.from_tag("dc:title") 75 self.get_meta_body().append(element) 76 element.text = title 77 78 def get_description(self) -> str | None: 79 """Get the description of the document. Also known as comments. 80 81 Return: str (or None if inexistant) 82 """ 83 element = self.get_element("//dc:description") 84 if element is None: 85 return None 86 return element.text 87 88 # As named in OOo 89 get_comments = get_description 90 91 def set_description(self, description: str) -> None: 92 """Set the description of the document. Also known as comments. 93 94 Arguments: 95 96 description -- str 97 """ 98 element = self.get_element("//dc:description") 99 if element is None: 100 element = Element.from_tag("dc:description") 101 self.get_meta_body().append(element) 102 element.text = description 103 104 set_comments = set_description 105 106 def get_subject(self) -> str | None: 107 """Get the subject of the document. 108 109 Return: str (or None if inexistant) 110 """ 111 element = self.get_element("//dc:subject") 112 if element is None: 113 return None 114 return element.text 115 116 def set_subject(self, subject: str) -> None: 117 """Set the subject of the document. 118 119 Arguments: 120 121 subject -- str 122 """ 123 element = self.get_element("//dc:subject") 124 if element is None: 125 element = Element.from_tag("dc:subject") 126 self.get_meta_body().append(element) 127 element.text = subject 128 129 def get_language(self) -> str | None: 130 """Get the language code of the document. 131 132 Return: str (or None if inexistant) 133 134 Example:: 135 136 >>> document.meta.get_language() 137 fr-FR 138 """ 139 element = self.get_element("//dc:language") 140 if element is None: 141 return None 142 return element.text 143 144 def set_language(self, language: str) -> None: 145 """Set the language code of the document. 146 147 Arguments: 148 149 language -- str 150 151 Example:: 152 153 >>> document.meta.set_language('fr-FR') 154 """ 155 language = str(language) 156 if not self._is_RFC3066(language): 157 raise TypeError( 158 'Language must be "xx" lang or "xx-YY" lang-COUNTRY code (RFC3066)' 159 ) 160 element = self.get_element("//dc:language") 161 if element is None: 162 element = Element.from_tag("dc:language") 163 self.get_meta_body().append(element) 164 element.text = language 165 166 @staticmethod 167 def _is_RFC3066(lang: str) -> bool: 168 def test_part1(part1: str) -> bool: 169 if not 2 <= len(part1) <= 3: 170 return False 171 return all(x in ascii_letters for x in part1) 172 173 def test_part2(part2: str) -> bool: 174 return all(x in ascii_letters or x in digits for x in part2) 175 176 if not lang or not isinstance(lang, str): 177 return False 178 if "-" not in lang: 179 return test_part1(lang) 180 parts = lang.split("-") 181 if len(parts) > 3: 182 return False 183 if not test_part1(parts[0]): 184 return False 185 return all(test_part2(p) for p in parts[1:]) 186 187 def get_modification_date(self) -> datetime | None: 188 """Get the last modified date of the document. 189 190 Return: datetime (or None if inexistant) 191 """ 192 element = self.get_element("//dc:date") 193 if element is None: 194 return None 195 modification_date = element.text 196 return DateTime.decode(modification_date) 197 198 def set_modification_date(self, date: datetime) -> None: 199 """Set the last modified date of the document. 200 201 Arguments: 202 203 date -- datetime 204 """ 205 element = self.get_element("//dc:date") 206 if element is None: 207 element = Element.from_tag("dc:date") 208 self.get_meta_body().append(element) 209 element.text = DateTime.encode(date) 210 211 def get_creation_date(self) -> datetime | None: 212 """Get the creation date of the document. 213 214 Return: datetime (or None if inexistant) 215 """ 216 element = self.get_element("//meta:creation-date") 217 if element is None: 218 return None 219 creation_date = element.text 220 return DateTime.decode(creation_date) 221 222 def set_creation_date(self, date: datetime) -> None: 223 """Set the creation date of the document. 224 225 Arguments: 226 227 date -- datetime 228 """ 229 element = self.get_element("//meta:creation-date") 230 if element is None: 231 element = Element.from_tag("meta:creation-date") 232 self.get_meta_body().append(element) 233 element.text = DateTime.encode(date) 234 235 def get_initial_creator(self) -> str | None: 236 """Get the first creator of the document. 237 238 Return: str (or None if inexistant) 239 240 Example:: 241 242 >>> document.meta.get_initial_creator() 243 Unknown 244 """ 245 element = self.get_element("//meta:initial-creator") 246 if element is None: 247 return None 248 return element.text 249 250 def set_initial_creator(self, creator: str) -> None: 251 """Set the first creator of the document. 252 253 Arguments: 254 255 creator -- str 256 257 Example:: 258 259 >>> document.meta.set_initial_creator("Plato") 260 """ 261 element = self.get_element("//meta:initial-creator") 262 if element is None: 263 element = Element.from_tag("meta:initial-creator") 264 self.get_meta_body().append(element) 265 element.text = creator 266 267 def get_creator(self) -> str | None: 268 """Get the creator of the document. 269 270 Return: str (or None if inexistant) 271 272 Example:: 273 274 >>> document.meta.get_creator() 275 Unknown 276 """ 277 element = self.get_element("//dc:creator") 278 if element is None: 279 return None 280 return element.text 281 282 def set_creator(self, creator: str) -> None: 283 """Set the creator of the document. 284 285 Arguments: 286 287 creator -- str 288 289 Example:: 290 291 >>> document.meta.set_creator("Plato") 292 """ 293 element = self.get_element("//dc:creator") 294 if element is None: 295 element = Element.from_tag("dc:creator") 296 self.get_meta_body().append(element) 297 element.text = creator 298 299 def get_keywords(self) -> str | None: 300 """Get the keywords of the document. Return the field as-is, without 301 any assumption on the keyword separator. 302 303 Return: str (or None if inexistant) 304 """ 305 element = self.get_element("//meta:keyword") 306 if element is None: 307 return None 308 return element.text 309 310 def set_keywords(self, keywords: str) -> None: 311 """Set the keywords of the document. Although the name is plural, a 312 str string is required, so join your list first. 313 314 Arguments: 315 316 keywords -- str 317 """ 318 element = self.get_element("//meta:keyword") 319 if element is None: 320 element = Element.from_tag("meta:keyword") 321 self.get_meta_body().append(element) 322 element.text = keywords 323 324 def get_editing_duration(self) -> timedelta | None: 325 """Get the time the document was edited, as reported by the 326 generator. 327 328 Return: timedelta (or None if inexistant) 329 """ 330 element = self.get_element("//meta:editing-duration") 331 if element is None: 332 return None 333 duration = element.text 334 return Duration.decode(duration) 335 336 def set_editing_duration(self, duration: timedelta) -> None: 337 """Set the time the document was edited. 338 339 Arguments: 340 341 duration -- timedelta 342 """ 343 if not isinstance(duration, timedelta): 344 raise TypeError("duration must be a timedelta") 345 element = self.get_element("//meta:editing-duration") 346 if element is None: 347 element = Element.from_tag("meta:editing-duration") 348 self.get_meta_body().append(element) 349 element.text = Duration.encode(duration) 350 351 def get_editing_cycles(self) -> int | None: 352 """Get the number of times the document was edited, as reported by 353 the generator. 354 355 Return: int (or None if inexistant) 356 """ 357 element = self.get_element("//meta:editing-cycles") 358 if element is None: 359 return None 360 cycles = element.text 361 return int(cycles) 362 363 def set_editing_cycles(self, cycles: int) -> None: 364 """Set the number of times the document was edited. 365 366 Arguments: 367 368 cycles -- int 369 """ 370 if not isinstance(cycles, int): 371 raise TypeError("cycles must be an int") 372 if cycles < 1: 373 raise ValueError("cycles must be a positive int") 374 element = self.get_element("//meta:editing-cycles") 375 if element is None: 376 element = Element.from_tag("meta:editing-cycles") 377 self.get_meta_body().append(element) 378 element.text = str(cycles) 379 380 def get_generator(self) -> str | None: 381 """Get the signature of the software that generated this document. 382 383 Return: str (or None if inexistant) 384 385 Example:: 386 387 >>> document.meta.get_generator() 388 KOffice/2.0.0 389 """ 390 element = self.get_element("//meta:generator") 391 if element is None: 392 return None 393 return element.text 394 395 def set_generator(self, generator: str) -> None: 396 """Set the signature of the software that generated this document. 397 398 Arguments: 399 400 generator -- str 401 402 Example:: 403 404 >>> document.meta.set_generator("Odfdo experiment") 405 """ 406 element = self.get_element("//meta:generator") 407 if element is None: 408 element = Element.from_tag("meta:generator") 409 self.get_meta_body().append(element) 410 element.text = generator 411 self._generator_modified = True 412 413 def set_generator_default(self) -> None: 414 """Set the signature of the software that generated this document 415 to ourself. 416 417 Example:: 418 419 >>> document.meta.set_generator_default() 420 """ 421 if not self._generator_modified: 422 self.set_generator(GENERATOR) 423 424 def get_statistic(self) -> dict[str, int] | None: 425 """Get the statistic from the software that generated this document. 426 427 Return: dict (or None if inexistant) 428 429 Example:: 430 431 >>> document.get_statistic(): 432 {'meta:table-count': 1, 433 'meta:image-count': 2, 434 'meta:object-count': 3, 435 'meta:page-count': 4, 436 'meta:paragraph-count': 5, 437 'meta:word-count': 6, 438 'meta:character-count': 7} 439 """ 440 element = self.get_element("//meta:document-statistic") 441 if element is None: 442 return None 443 statistic = {} 444 for key, value in element.attributes.items(): 445 statistic[to_str(key)] = int(value) 446 return statistic 447 448 def set_statistic(self, statistic: dict[str, int]) -> None: 449 """Set the statistic for the documents: number of words, paragraphs, 450 etc. 451 452 Arguments: 453 454 statistic -- dict 455 456 Example:: 457 458 >>> statistic = {'meta:table-count': 1, 459 'meta:image-count': 2, 460 'meta:object-count': 3, 461 'meta:page-count': 4, 462 'meta:paragraph-count': 5, 463 'meta:word-count': 6, 464 'meta:character-count': 7} 465 >>> document.meta.set_statistic(statistic) 466 """ 467 if not isinstance(statistic, dict): 468 raise TypeError("Statistic must be a dict") 469 element = self.get_element("//meta:document-statistic") 470 for key, value in statistic.items(): 471 try: 472 ivalue = int(value) 473 except ValueError as e: 474 raise TypeError("Statistic value must be a int") from e 475 element.set_attribute(to_str(key), str(ivalue)) 476 477 def get_user_defined_metadata(self) -> dict[str, Any]: 478 """Return a dict of str/value mapping. 479 480 Value types can be: Decimal, date, time, boolean or str. 481 """ 482 result: dict[str, Any] = {} 483 for item in self.get_elements("//meta:user-defined"): 484 if not isinstance(item, Element): 485 continue 486 # Read the values 487 name = item.get_attribute_string("meta:name") 488 if name is None: 489 continue 490 value = self._get_meta_value(item) 491 result[name] = value 492 return result 493 494 def get_user_defined_metadata_of_name(self, keyname: str) -> dict[str, Any] | None: 495 """Return the content of the user defined metadata of that name. 496 Return None if no name matchs or a dic of fields. 497 498 Arguments: 499 500 name -- string, name (meta:name content) 501 """ 502 result = {} 503 found = False 504 for item in self.get_elements("//meta:user-defined"): 505 if not isinstance(item, Element): 506 continue 507 # Read the values 508 name = item.get_attribute("meta:name") 509 if name == keyname: 510 found = True 511 break 512 if not found: 513 return None 514 result["name"] = name 515 value, value_type, text = self._get_meta_value(item, full=True) # type: ignore 516 result["value"] = value 517 result["value_type"] = value_type 518 result["text"] = text 519 return result 520 521 def set_user_defined_metadata(self, name: str, value: Any) -> None: 522 if isinstance(value, bool): 523 value_type = "boolean" 524 value = "true" if value else "false" 525 elif isinstance(value, (int, float, Decimal)): 526 value_type = "float" 527 value = str(value) 528 elif isinstance(value, dtdate): 529 value_type = "date" 530 value = str(Date.encode(value)) 531 elif isinstance(value, datetime): 532 value_type = "date" 533 value = str(DateTime.encode(value)) 534 elif isinstance(value, str): 535 value_type = "string" 536 elif isinstance(value, timedelta): 537 value_type = "time" 538 value = str(Duration.encode(value)) 539 else: 540 raise TypeError('unexpected type "%s" for value' % type(value)) 541 # Already the same element ? 542 for metadata in self.get_elements("//meta:user-defined"): 543 if not isinstance(metadata, Element): 544 continue 545 if metadata.get_attribute("meta:name") == name: 546 break 547 else: 548 metadata = Element.from_tag("meta:user-defined") 549 metadata.set_attribute("meta:name", name) 550 self.get_meta_body().append(metadata) 551 metadata.set_attribute("meta:value-type", value_type) 552 metadata.text = value 553 554 def _get_meta_value( 555 self, element: Element, full: bool = False 556 ) -> Any | tuple[Any, str, str]: 557 """get_value() deicated to the meta data part, for one meta element.""" 558 if full: 559 return self._get_meta_value_full(element) 560 else: 561 return self._get_meta_value_full(element)[0] 562 563 @staticmethod 564 def _get_meta_value_full(element: Element) -> tuple[Any, str, str]: 565 """get_value deicated to the meta data part, for one meta element.""" 566 # name = element.get_attribute('meta:name') 567 value_type = element.get_attribute_string("meta:value-type") 568 if value_type is None: 569 value_type = "string" 570 text = element.text 571 # Interpretation 572 if value_type == "boolean": 573 return (Boolean.decode(text), value_type, text) 574 if value_type in ("float", "percentage", "currency"): 575 return (Decimal(text), value_type, text) 576 if value_type == "date": 577 if "T" in text: 578 return (DateTime.decode(text), value_type, text) 579 else: 580 return (Date.decode(text), value_type, text) 581 if value_type == "string": 582 return (text, value_type, text) 583 if value_type == "time": 584 return (Duration.decode(text), value_type, text) 585 raise TypeError(f"Unknown value type: '{value_type!r}'")
Representation of an XML part.
Abstraction of the XML library behind.
51 def get_title(self) -> str | None: 52 """Get the title of the document. 53 54 This is not the first heading but the title metadata. 55 56 Return: str (or None if inexistant) 57 """ 58 element = self.get_element("//dc:title") 59 if element is None: 60 return None 61 return element.text
Get the title of the document.
This is not the first heading but the title metadata.
Return: str (or None if inexistant)
63 def set_title(self, title: str) -> None: 64 """Set the title of the document. 65 66 This is not the first heading but the title metadata. 67 68 Arguments: 69 70 title -- str 71 """ 72 element = self.get_element("//dc:title") 73 if element is None: 74 element = Element.from_tag("dc:title") 75 self.get_meta_body().append(element) 76 element.text = title
Set the title of the document.
This is not the first heading but the title metadata.
Arguments:
title -- str
78 def get_description(self) -> str | None: 79 """Get the description of the document. Also known as comments. 80 81 Return: str (or None if inexistant) 82 """ 83 element = self.get_element("//dc:description") 84 if element is None: 85 return None 86 return element.text
Get the description of the document. Also known as comments.
Return: str (or None if inexistant)
78 def get_description(self) -> str | None: 79 """Get the description of the document. Also known as comments. 80 81 Return: str (or None if inexistant) 82 """ 83 element = self.get_element("//dc:description") 84 if element is None: 85 return None 86 return element.text
Get the description of the document. Also known as comments.
Return: str (or None if inexistant)
91 def set_description(self, description: str) -> None: 92 """Set the description of the document. Also known as comments. 93 94 Arguments: 95 96 description -- str 97 """ 98 element = self.get_element("//dc:description") 99 if element is None: 100 element = Element.from_tag("dc:description") 101 self.get_meta_body().append(element) 102 element.text = description
Set the description of the document. Also known as comments.
Arguments:
description -- str
91 def set_description(self, description: str) -> None: 92 """Set the description of the document. Also known as comments. 93 94 Arguments: 95 96 description -- str 97 """ 98 element = self.get_element("//dc:description") 99 if element is None: 100 element = Element.from_tag("dc:description") 101 self.get_meta_body().append(element) 102 element.text = description
Set the description of the document. Also known as comments.
Arguments:
description -- str
106 def get_subject(self) -> str | None: 107 """Get the subject of the document. 108 109 Return: str (or None if inexistant) 110 """ 111 element = self.get_element("//dc:subject") 112 if element is None: 113 return None 114 return element.text
Get the subject of the document.
Return: str (or None if inexistant)
116 def set_subject(self, subject: str) -> None: 117 """Set the subject of the document. 118 119 Arguments: 120 121 subject -- str 122 """ 123 element = self.get_element("//dc:subject") 124 if element is None: 125 element = Element.from_tag("dc:subject") 126 self.get_meta_body().append(element) 127 element.text = subject
Set the subject of the document.
Arguments:
subject -- str
129 def get_language(self) -> str | None: 130 """Get the language code of the document. 131 132 Return: str (or None if inexistant) 133 134 Example:: 135 136 >>> document.meta.get_language() 137 fr-FR 138 """ 139 element = self.get_element("//dc:language") 140 if element is None: 141 return None 142 return element.text
Get the language code of the document.
Return: str (or None if inexistant)
Example::
>>> document.meta.get_language()
fr-FR
144 def set_language(self, language: str) -> None: 145 """Set the language code of the document. 146 147 Arguments: 148 149 language -- str 150 151 Example:: 152 153 >>> document.meta.set_language('fr-FR') 154 """ 155 language = str(language) 156 if not self._is_RFC3066(language): 157 raise TypeError( 158 'Language must be "xx" lang or "xx-YY" lang-COUNTRY code (RFC3066)' 159 ) 160 element = self.get_element("//dc:language") 161 if element is None: 162 element = Element.from_tag("dc:language") 163 self.get_meta_body().append(element) 164 element.text = language
Set the language code of the document.
Arguments:
language -- str
Example::
>>> document.meta.set_language('fr-FR')
187 def get_modification_date(self) -> datetime | None: 188 """Get the last modified date of the document. 189 190 Return: datetime (or None if inexistant) 191 """ 192 element = self.get_element("//dc:date") 193 if element is None: 194 return None 195 modification_date = element.text 196 return DateTime.decode(modification_date)
Get the last modified date of the document.
Return: datetime (or None if inexistant)
198 def set_modification_date(self, date: datetime) -> None: 199 """Set the last modified date of the document. 200 201 Arguments: 202 203 date -- datetime 204 """ 205 element = self.get_element("//dc:date") 206 if element is None: 207 element = Element.from_tag("dc:date") 208 self.get_meta_body().append(element) 209 element.text = DateTime.encode(date)
Set the last modified date of the document.
Arguments:
date -- datetime
211 def get_creation_date(self) -> datetime | None: 212 """Get the creation date of the document. 213 214 Return: datetime (or None if inexistant) 215 """ 216 element = self.get_element("//meta:creation-date") 217 if element is None: 218 return None 219 creation_date = element.text 220 return DateTime.decode(creation_date)
Get the creation date of the document.
Return: datetime (or None if inexistant)
222 def set_creation_date(self, date: datetime) -> None: 223 """Set the creation date of the document. 224 225 Arguments: 226 227 date -- datetime 228 """ 229 element = self.get_element("//meta:creation-date") 230 if element is None: 231 element = Element.from_tag("meta:creation-date") 232 self.get_meta_body().append(element) 233 element.text = DateTime.encode(date)
Set the creation date of the document.
Arguments:
date -- datetime
235 def get_initial_creator(self) -> str | None: 236 """Get the first creator of the document. 237 238 Return: str (or None if inexistant) 239 240 Example:: 241 242 >>> document.meta.get_initial_creator() 243 Unknown 244 """ 245 element = self.get_element("//meta:initial-creator") 246 if element is None: 247 return None 248 return element.text
Get the first creator of the document.
Return: str (or None if inexistant)
Example::
>>> document.meta.get_initial_creator()
Unknown
250 def set_initial_creator(self, creator: str) -> None: 251 """Set the first creator of the document. 252 253 Arguments: 254 255 creator -- str 256 257 Example:: 258 259 >>> document.meta.set_initial_creator("Plato") 260 """ 261 element = self.get_element("//meta:initial-creator") 262 if element is None: 263 element = Element.from_tag("meta:initial-creator") 264 self.get_meta_body().append(element) 265 element.text = creator
Set the first creator of the document.
Arguments:
creator -- str
Example::
>>> document.meta.set_initial_creator("Plato")
267 def get_creator(self) -> str | None: 268 """Get the creator of the document. 269 270 Return: str (or None if inexistant) 271 272 Example:: 273 274 >>> document.meta.get_creator() 275 Unknown 276 """ 277 element = self.get_element("//dc:creator") 278 if element is None: 279 return None 280 return element.text
Get the creator of the document.
Return: str (or None if inexistant)
Example::
>>> document.meta.get_creator()
Unknown
282 def set_creator(self, creator: str) -> None: 283 """Set the creator of the document. 284 285 Arguments: 286 287 creator -- str 288 289 Example:: 290 291 >>> document.meta.set_creator("Plato") 292 """ 293 element = self.get_element("//dc:creator") 294 if element is None: 295 element = Element.from_tag("dc:creator") 296 self.get_meta_body().append(element) 297 element.text = creator
Set the creator of the document.
Arguments:
creator -- str
Example::
>>> document.meta.set_creator("Plato")
299 def get_keywords(self) -> str | None: 300 """Get the keywords of the document. Return the field as-is, without 301 any assumption on the keyword separator. 302 303 Return: str (or None if inexistant) 304 """ 305 element = self.get_element("//meta:keyword") 306 if element is None: 307 return None 308 return element.text
Get the keywords of the document. Return the field as-is, without any assumption on the keyword separator.
Return: str (or None if inexistant)
310 def set_keywords(self, keywords: str) -> None: 311 """Set the keywords of the document. Although the name is plural, a 312 str string is required, so join your list first. 313 314 Arguments: 315 316 keywords -- str 317 """ 318 element = self.get_element("//meta:keyword") 319 if element is None: 320 element = Element.from_tag("meta:keyword") 321 self.get_meta_body().append(element) 322 element.text = keywords
Set the keywords of the document. Although the name is plural, a str string is required, so join your list first.
Arguments:
keywords -- str
324 def get_editing_duration(self) -> timedelta | None: 325 """Get the time the document was edited, as reported by the 326 generator. 327 328 Return: timedelta (or None if inexistant) 329 """ 330 element = self.get_element("//meta:editing-duration") 331 if element is None: 332 return None 333 duration = element.text 334 return Duration.decode(duration)
Get the time the document was edited, as reported by the generator.
Return: timedelta (or None if inexistant)
336 def set_editing_duration(self, duration: timedelta) -> None: 337 """Set the time the document was edited. 338 339 Arguments: 340 341 duration -- timedelta 342 """ 343 if not isinstance(duration, timedelta): 344 raise TypeError("duration must be a timedelta") 345 element = self.get_element("//meta:editing-duration") 346 if element is None: 347 element = Element.from_tag("meta:editing-duration") 348 self.get_meta_body().append(element) 349 element.text = Duration.encode(duration)
Set the time the document was edited.
Arguments:
duration -- timedelta
351 def get_editing_cycles(self) -> int | None: 352 """Get the number of times the document was edited, as reported by 353 the generator. 354 355 Return: int (or None if inexistant) 356 """ 357 element = self.get_element("//meta:editing-cycles") 358 if element is None: 359 return None 360 cycles = element.text 361 return int(cycles)
Get the number of times the document was edited, as reported by the generator.
Return: int (or None if inexistant)
363 def set_editing_cycles(self, cycles: int) -> None: 364 """Set the number of times the document was edited. 365 366 Arguments: 367 368 cycles -- int 369 """ 370 if not isinstance(cycles, int): 371 raise TypeError("cycles must be an int") 372 if cycles < 1: 373 raise ValueError("cycles must be a positive int") 374 element = self.get_element("//meta:editing-cycles") 375 if element is None: 376 element = Element.from_tag("meta:editing-cycles") 377 self.get_meta_body().append(element) 378 element.text = str(cycles)
Set the number of times the document was edited.
Arguments:
cycles -- int
380 def get_generator(self) -> str | None: 381 """Get the signature of the software that generated this document. 382 383 Return: str (or None if inexistant) 384 385 Example:: 386 387 >>> document.meta.get_generator() 388 KOffice/2.0.0 389 """ 390 element = self.get_element("//meta:generator") 391 if element is None: 392 return None 393 return element.text
Get the signature of the software that generated this document.
Return: str (or None if inexistant)
Example::
>>> document.meta.get_generator()
KOffice/2.0.0
395 def set_generator(self, generator: str) -> None: 396 """Set the signature of the software that generated this document. 397 398 Arguments: 399 400 generator -- str 401 402 Example:: 403 404 >>> document.meta.set_generator("Odfdo experiment") 405 """ 406 element = self.get_element("//meta:generator") 407 if element is None: 408 element = Element.from_tag("meta:generator") 409 self.get_meta_body().append(element) 410 element.text = generator 411 self._generator_modified = True
Set the signature of the software that generated this document.
Arguments:
generator -- str
Example::
>>> document.meta.set_generator("Odfdo experiment")
413 def set_generator_default(self) -> None: 414 """Set the signature of the software that generated this document 415 to ourself. 416 417 Example:: 418 419 >>> document.meta.set_generator_default() 420 """ 421 if not self._generator_modified: 422 self.set_generator(GENERATOR)
Set the signature of the software that generated this document to ourself.
Example::
>>> document.meta.set_generator_default()
424 def get_statistic(self) -> dict[str, int] | None: 425 """Get the statistic from the software that generated this document. 426 427 Return: dict (or None if inexistant) 428 429 Example:: 430 431 >>> document.get_statistic(): 432 {'meta:table-count': 1, 433 'meta:image-count': 2, 434 'meta:object-count': 3, 435 'meta:page-count': 4, 436 'meta:paragraph-count': 5, 437 'meta:word-count': 6, 438 'meta:character-count': 7} 439 """ 440 element = self.get_element("//meta:document-statistic") 441 if element is None: 442 return None 443 statistic = {} 444 for key, value in element.attributes.items(): 445 statistic[to_str(key)] = int(value) 446 return statistic
Get the statistic from the software that generated this document.
Return: dict (or None if inexistant)
Example::
>>> document.get_statistic():
{'meta:table-count': 1,
'meta:image-count': 2,
'meta:object-count': 3,
'meta:page-count': 4,
'meta:paragraph-count': 5,
'meta:word-count': 6,
'meta:character-count': 7}
448 def set_statistic(self, statistic: dict[str, int]) -> None: 449 """Set the statistic for the documents: number of words, paragraphs, 450 etc. 451 452 Arguments: 453 454 statistic -- dict 455 456 Example:: 457 458 >>> statistic = {'meta:table-count': 1, 459 'meta:image-count': 2, 460 'meta:object-count': 3, 461 'meta:page-count': 4, 462 'meta:paragraph-count': 5, 463 'meta:word-count': 6, 464 'meta:character-count': 7} 465 >>> document.meta.set_statistic(statistic) 466 """ 467 if not isinstance(statistic, dict): 468 raise TypeError("Statistic must be a dict") 469 element = self.get_element("//meta:document-statistic") 470 for key, value in statistic.items(): 471 try: 472 ivalue = int(value) 473 except ValueError as e: 474 raise TypeError("Statistic value must be a int") from e 475 element.set_attribute(to_str(key), str(ivalue))
Set the statistic for the documents: number of words, paragraphs, etc.
Arguments:
statistic -- dict
Example::
>>> statistic = {'meta:table-count': 1,
'meta:image-count': 2,
'meta:object-count': 3,
'meta:page-count': 4,
'meta:paragraph-count': 5,
'meta:word-count': 6,
'meta:character-count': 7}
>>> document.meta.set_statistic(statistic)
477 def get_user_defined_metadata(self) -> dict[str, Any]: 478 """Return a dict of str/value mapping. 479 480 Value types can be: Decimal, date, time, boolean or str. 481 """ 482 result: dict[str, Any] = {} 483 for item in self.get_elements("//meta:user-defined"): 484 if not isinstance(item, Element): 485 continue 486 # Read the values 487 name = item.get_attribute_string("meta:name") 488 if name is None: 489 continue 490 value = self._get_meta_value(item) 491 result[name] = value 492 return result
Return a dict of str/value mapping.
Value types can be: Decimal, date, time, boolean or str.
494 def get_user_defined_metadata_of_name(self, keyname: str) -> dict[str, Any] | None: 495 """Return the content of the user defined metadata of that name. 496 Return None if no name matchs or a dic of fields. 497 498 Arguments: 499 500 name -- string, name (meta:name content) 501 """ 502 result = {} 503 found = False 504 for item in self.get_elements("//meta:user-defined"): 505 if not isinstance(item, Element): 506 continue 507 # Read the values 508 name = item.get_attribute("meta:name") 509 if name == keyname: 510 found = True 511 break 512 if not found: 513 return None 514 result["name"] = name 515 value, value_type, text = self._get_meta_value(item, full=True) # type: ignore 516 result["value"] = value 517 result["value_type"] = value_type 518 result["text"] = text 519 return result
Return the content of the user defined metadata of that name. Return None if no name matchs or a dic of fields.
Arguments:
name -- string, name (meta:name content)
521 def set_user_defined_metadata(self, name: str, value: Any) -> None: 522 if isinstance(value, bool): 523 value_type = "boolean" 524 value = "true" if value else "false" 525 elif isinstance(value, (int, float, Decimal)): 526 value_type = "float" 527 value = str(value) 528 elif isinstance(value, dtdate): 529 value_type = "date" 530 value = str(Date.encode(value)) 531 elif isinstance(value, datetime): 532 value_type = "date" 533 value = str(DateTime.encode(value)) 534 elif isinstance(value, str): 535 value_type = "string" 536 elif isinstance(value, timedelta): 537 value_type = "time" 538 value = str(Duration.encode(value)) 539 else: 540 raise TypeError('unexpected type "%s" for value' % type(value)) 541 # Already the same element ? 542 for metadata in self.get_elements("//meta:user-defined"): 543 if not isinstance(metadata, Element): 544 continue 545 if metadata.get_attribute("meta:name") == name: 546 break 547 else: 548 metadata = Element.from_tag("meta:user-defined") 549 metadata.set_attribute("meta:name", name) 550 self.get_meta_body().append(metadata) 551 metadata.set_attribute("meta:value-type", value_type) 552 metadata.text = value
Inherited Members
2909class NamedRange(Element): 2910 """ODF Named Range "table:named-range". Identifies inside the spreadsheet 2911 a range of cells of a table by a name and the name of the table. 2912 2913 Name Ranges have the following attributes: 2914 2915 name -- name of the named range 2916 2917 table_name -- name of the table 2918 2919 start -- first cell of the named range, tuple (x, y) 2920 2921 end -- last cell of the named range, tuple (x, y) 2922 2923 crange -- range of the named range, tuple (x, y, z, t) 2924 2925 usage -- None or str, usage of the named range. 2926 """ 2927 2928 _tag = "table:named-range" 2929 2930 def __init__( 2931 self, 2932 name: str | None = None, 2933 crange: str | tuple | list | None = None, 2934 table_name: str | None = None, 2935 usage: str | None = None, 2936 **kwargs: Any, 2937 ) -> None: 2938 """Create a Named Range element. 'name' must contains only letters, digits 2939 and '_', and must not be like a coordinate as 'A1'. 'table_name' must be 2940 a correct table name (no "'" or "/" in it). 2941 2942 Arguments: 2943 2944 name -- str, name of the named range 2945 2946 crange -- str or tuple of int, cell or area coordinate 2947 2948 table_name -- str, name of the table 2949 2950 usage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row' 2951 """ 2952 super().__init__(**kwargs) 2953 self.usage = None 2954 if self._do_init: 2955 self.name = name or "" 2956 self.table_name = _table_name_check(table_name) 2957 self.set_range(crange or "") 2958 self.set_usage(usage) 2959 cell_range_address = self.get_attribute_string("table:cell-range-address") or "" 2960 if not cell_range_address: 2961 self.table_name = "" 2962 self.start = None 2963 self.end = None 2964 self.crange = None 2965 self.usage = None 2966 return 2967 self.usage = self.get_attribute("table:range-usable-as") 2968 name_range = cell_range_address.replace("$", "") 2969 name, crange = name_range.split(".", 1) 2970 if name.startswith("'") and name.endswith("'"): 2971 name = name[1:-1] 2972 self.table_name = name 2973 crange = crange.replace(".", "") 2974 self._set_range(crange) 2975 2976 def set_usage(self, usage: str | None = None) -> None: 2977 """Set the usage of the Named Range. Usage can be None (default) or one 2978 of : 2979 'print-range' 2980 'filter' 2981 'repeat-column' 2982 'repeat-row' 2983 2984 Arguments: 2985 2986 usage -- None or str 2987 """ 2988 if usage is not None: 2989 usage = usage.strip().lower() 2990 if usage not in ("print-range", "filter", "repeat-column", "repeat-row"): 2991 usage = None 2992 if usage is None: 2993 with contextlib.suppress(KeyError): 2994 self.del_attribute("table:range-usable-as") 2995 self.usage = None 2996 else: 2997 self.set_attribute("table:range-usable-as", usage) 2998 self.usage = usage 2999 3000 @property 3001 def name(self) -> str | None: 3002 """Get / set the name of the table.""" 3003 return self.get_attribute_string("table:name") 3004 3005 @name.setter 3006 def name(self, name: str) -> None: 3007 """Set the name of the Named Range. The name is mandatory, if a Named 3008 Range of the same name exists, it will be replaced. Name must contains 3009 only alphanumerics characters and '_', and can not be of a cell 3010 coordinates form like 'AB12'. 3011 3012 Arguments: 3013 3014 name -- str 3015 """ 3016 name = name.strip() 3017 if not name: 3018 raise ValueError("Name required.") 3019 for x in name: 3020 if x in forbidden_in_named_range(): 3021 raise ValueError(f"Character forbidden '{x}' ") 3022 step = "" 3023 for x in name: 3024 if x in string.ascii_letters and step in ("", "A"): 3025 step = "A" 3026 continue 3027 elif step in ("A", "A1") and x in string.digits: 3028 step = "A1" 3029 continue 3030 else: 3031 step = "" 3032 break 3033 if step == "A1": 3034 raise ValueError("Name of the type 'ABC123' is not allowed.") 3035 with contextlib.suppress(Exception): 3036 # we are not on an inserted in a document. 3037 body = self.document_body 3038 named_range = body.get_named_range(name) # type: ignore 3039 if named_range: 3040 named_range.delete() 3041 self.set_attribute("table:name", name) 3042 3043 def set_table_name(self, name: str) -> None: 3044 """Set the name of the table of the Named Range. The name is mandatory. 3045 3046 Arguments: 3047 3048 name -- str 3049 """ 3050 self.table_name = _table_name_check(name) 3051 self._update_attributes() 3052 3053 def _set_range(self, coord: tuple | list | str) -> None: 3054 digits = convert_coordinates(coord) 3055 if len(digits) == 4: 3056 x, y, z, t = digits 3057 else: 3058 x, y = digits 3059 z, t = digits 3060 self.start = x, y # type: ignore 3061 self.end = z, t # type: ignore 3062 self.crange = x, y, z, t # type: ignore 3063 3064 def set_range(self, crange: str | tuple | list) -> None: 3065 """Set the range of the named range. Range can be either one cell 3066 (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric 3067 value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0). 3068 3069 Arguments: 3070 3071 crange -- str or tuple of int, cell or area coordinate 3072 """ 3073 self._set_range(crange) 3074 self._update_attributes() 3075 3076 def _update_attributes(self) -> None: 3077 self.set_attribute("table:base-cell-address", self._make_base_cell_address()) 3078 self.set_attribute("table:cell-range-address", self._make_cell_range_address()) 3079 3080 def _make_base_cell_address(self) -> str: 3081 # assuming we got table_name and range 3082 if " " in self.table_name: 3083 name = f"'{self.table_name}'" 3084 else: 3085 name = self.table_name 3086 return f"${name}.${digit_to_alpha(self.start[0])}${self.start[1] + 1}" # type: ignore 3087 3088 def _make_cell_range_address(self) -> str: 3089 # assuming we got table_name and range 3090 if " " in self.table_name: 3091 name = f"'{self.table_name}'" 3092 else: 3093 name = self.table_name 3094 if self.start == self.end: 3095 return self._make_base_cell_address() 3096 return ( 3097 f"${name}.${digit_to_alpha(self.start[0])}${self.start[1] + 1}:" # type: ignore 3098 f".${digit_to_alpha(self.end[0])}${self.end[1] + 1}" # type: ignore 3099 ) 3100 3101 def get_values( 3102 self, 3103 cell_type: str | None = None, 3104 complete: bool = True, 3105 get_type: bool = False, 3106 flat: bool = False, 3107 ) -> list: 3108 """Shortcut to retrieve the values of the cells of the named range. See 3109 table.get_values() for the arguments description and return format. 3110 """ 3111 body = self.document_body 3112 if not body: 3113 raise ValueError("Table is not inside a document.") 3114 table = body.get_table(name=self.table_name) 3115 if table is None: 3116 raise ValueError 3117 return table.get_values(self.crange, cell_type, complete, get_type, flat) # type: ignore 3118 3119 def get_value(self, get_type: bool = False) -> Any: 3120 """Shortcut to retrieve the value of the first cell of the named range. 3121 See table.get_value() for the arguments description and return format. 3122 """ 3123 body = self.document_body 3124 if not body: 3125 raise ValueError("Table is not inside a document.") 3126 table = body.get_table(name=self.table_name) 3127 if table is None: 3128 raise ValueError 3129 return table.get_value(self.start, get_type) # type: ignore 3130 3131 def set_values( 3132 self, 3133 values: list, 3134 style: str | None = None, 3135 cell_type: str | None = None, 3136 currency: str | None = None, 3137 ) -> None: 3138 """Shortcut to set the values of the cells of the named range. 3139 See table.set_values() for the arguments description. 3140 """ 3141 body = self.document_body 3142 if not body: 3143 raise ValueError("Table is not inside a document.") 3144 table = body.get_table(name=self.table_name) 3145 if table is None: 3146 raise ValueError 3147 table.set_values( # type: ignore 3148 values, 3149 coord=self.crange, 3150 style=style, 3151 cell_type=cell_type, 3152 currency=currency, 3153 ) 3154 3155 def set_value( 3156 self, 3157 value: Any, 3158 cell_type: str | None = None, 3159 currency: str | None = None, 3160 style: str | None = None, 3161 ) -> None: 3162 """Shortcut to set the value of the first cell of the named range. 3163 See table.set_value() for the arguments description. 3164 """ 3165 body = self.document_body 3166 if not body: 3167 raise ValueError("Table is not inside a document.") 3168 table = body.get_table(name=self.table_name) 3169 if table is None: 3170 raise ValueError 3171 table.set_value( # type: ignore 3172 coord=self.start, 3173 value=value, 3174 cell_type=cell_type, 3175 currency=currency, 3176 style=style, 3177 )
ODF Named Range "table:named-range". Identifies inside the spreadsheet a range of cells of a table by a name and the name of the table.
Name Ranges have the following attributes:
name -- name of the named range
table_name -- name of the table
start -- first cell of the named range, tuple (x, y)
end -- last cell of the named range, tuple (x, y)
crange -- range of the named range, tuple (x, y, z, t)
usage -- None or str, usage of the named range.
2930 def __init__( 2931 self, 2932 name: str | None = None, 2933 crange: str | tuple | list | None = None, 2934 table_name: str | None = None, 2935 usage: str | None = None, 2936 **kwargs: Any, 2937 ) -> None: 2938 """Create a Named Range element. 'name' must contains only letters, digits 2939 and '_', and must not be like a coordinate as 'A1'. 'table_name' must be 2940 a correct table name (no "'" or "/" in it). 2941 2942 Arguments: 2943 2944 name -- str, name of the named range 2945 2946 crange -- str or tuple of int, cell or area coordinate 2947 2948 table_name -- str, name of the table 2949 2950 usage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row' 2951 """ 2952 super().__init__(**kwargs) 2953 self.usage = None 2954 if self._do_init: 2955 self.name = name or "" 2956 self.table_name = _table_name_check(table_name) 2957 self.set_range(crange or "") 2958 self.set_usage(usage) 2959 cell_range_address = self.get_attribute_string("table:cell-range-address") or "" 2960 if not cell_range_address: 2961 self.table_name = "" 2962 self.start = None 2963 self.end = None 2964 self.crange = None 2965 self.usage = None 2966 return 2967 self.usage = self.get_attribute("table:range-usable-as") 2968 name_range = cell_range_address.replace("$", "") 2969 name, crange = name_range.split(".", 1) 2970 if name.startswith("'") and name.endswith("'"): 2971 name = name[1:-1] 2972 self.table_name = name 2973 crange = crange.replace(".", "") 2974 self._set_range(crange)
Create a Named Range element. 'name' must contains only letters, digits and '_', and must not be like a coordinate as 'A1'. 'table_name' must be a correct table name (no "'" or "/" in it).
Arguments:
name -- str, name of the named range
crange -- str or tuple of int, cell or area coordinate
table_name -- str, name of the table
usage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row'
2976 def set_usage(self, usage: str | None = None) -> None: 2977 """Set the usage of the Named Range. Usage can be None (default) or one 2978 of : 2979 'print-range' 2980 'filter' 2981 'repeat-column' 2982 'repeat-row' 2983 2984 Arguments: 2985 2986 usage -- None or str 2987 """ 2988 if usage is not None: 2989 usage = usage.strip().lower() 2990 if usage not in ("print-range", "filter", "repeat-column", "repeat-row"): 2991 usage = None 2992 if usage is None: 2993 with contextlib.suppress(KeyError): 2994 self.del_attribute("table:range-usable-as") 2995 self.usage = None 2996 else: 2997 self.set_attribute("table:range-usable-as", usage) 2998 self.usage = usage
Set the usage of the Named Range. Usage can be None (default) or one of : 'print-range' 'filter' 'repeat-column' 'repeat-row'
Arguments:
usage -- None or str
3000 @property 3001 def name(self) -> str | None: 3002 """Get / set the name of the table.""" 3003 return self.get_attribute_string("table:name")
Get / set the name of the table.
3043 def set_table_name(self, name: str) -> None: 3044 """Set the name of the table of the Named Range. The name is mandatory. 3045 3046 Arguments: 3047 3048 name -- str 3049 """ 3050 self.table_name = _table_name_check(name) 3051 self._update_attributes()
Set the name of the table of the Named Range. The name is mandatory.
Arguments:
name -- str
3064 def set_range(self, crange: str | tuple | list) -> None: 3065 """Set the range of the named range. Range can be either one cell 3066 (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric 3067 value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0). 3068 3069 Arguments: 3070 3071 crange -- str or tuple of int, cell or area coordinate 3072 """ 3073 self._set_range(crange) 3074 self._update_attributes()
Set the range of the named range. Range can be either one cell (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0).
Arguments:
crange -- str or tuple of int, cell or area coordinate
3101 def get_values( 3102 self, 3103 cell_type: str | None = None, 3104 complete: bool = True, 3105 get_type: bool = False, 3106 flat: bool = False, 3107 ) -> list: 3108 """Shortcut to retrieve the values of the cells of the named range. See 3109 table.get_values() for the arguments description and return format. 3110 """ 3111 body = self.document_body 3112 if not body: 3113 raise ValueError("Table is not inside a document.") 3114 table = body.get_table(name=self.table_name) 3115 if table is None: 3116 raise ValueError 3117 return table.get_values(self.crange, cell_type, complete, get_type, flat) # type: ignore
Shortcut to retrieve the values of the cells of the named range. See table.get_values() for the arguments description and return format.
3119 def get_value(self, get_type: bool = False) -> Any: 3120 """Shortcut to retrieve the value of the first cell of the named range. 3121 See table.get_value() for the arguments description and return format. 3122 """ 3123 body = self.document_body 3124 if not body: 3125 raise ValueError("Table is not inside a document.") 3126 table = body.get_table(name=self.table_name) 3127 if table is None: 3128 raise ValueError 3129 return table.get_value(self.start, get_type) # type: ignore
Shortcut to retrieve the value of the first cell of the named range. See table.get_value() for the arguments description and return format.
3131 def set_values( 3132 self, 3133 values: list, 3134 style: str | None = None, 3135 cell_type: str | None = None, 3136 currency: str | None = None, 3137 ) -> None: 3138 """Shortcut to set the values of the cells of the named range. 3139 See table.set_values() for the arguments description. 3140 """ 3141 body = self.document_body 3142 if not body: 3143 raise ValueError("Table is not inside a document.") 3144 table = body.get_table(name=self.table_name) 3145 if table is None: 3146 raise ValueError 3147 table.set_values( # type: ignore 3148 values, 3149 coord=self.crange, 3150 style=style, 3151 cell_type=cell_type, 3152 currency=currency, 3153 )
Shortcut to set the values of the cells of the named range. See table.set_values() for the arguments description.
3155 def set_value( 3156 self, 3157 value: Any, 3158 cell_type: str | None = None, 3159 currency: str | None = None, 3160 style: str | None = None, 3161 ) -> None: 3162 """Shortcut to set the value of the first cell of the named range. 3163 See table.set_value() for the arguments description. 3164 """ 3165 body = self.document_body 3166 if not body: 3167 raise ValueError("Table is not inside a document.") 3168 table = body.get_table(name=self.table_name) 3169 if table is None: 3170 raise ValueError 3171 table.set_value( # type: ignore 3172 coord=self.start, 3173 value=value, 3174 cell_type=cell_type, 3175 currency=currency, 3176 style=style, 3177 )
Shortcut to set the value of the first cell of the named range. See table.set_value() for the arguments description.
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
57class Note(Element): 58 """Either a footnote or a endnote element with the given text, 59 optionally referencing it using the given note_id. 60 61 Arguments: 62 63 note_class -- 'footnote' or 'endnote' 64 65 note_id -- str 66 67 citation -- str 68 69 body -- str or Element 70 """ 71 72 _tag = "text:note" 73 _properties = ( 74 PropDef("note_class", "text:note-class"), 75 PropDef("note_id", "text:id"), 76 ) 77 78 def __init__( 79 self, 80 note_class: str = "footnote", 81 note_id: str | None = None, 82 citation: str | None = None, 83 body: str | None = None, 84 **kwargs: Any, 85 ) -> None: 86 super().__init__(**kwargs) 87 if self._do_init: 88 self.insert(Element.from_tag("text:note-body"), position=0) 89 self.insert(Element.from_tag("text:note-citation"), position=0) 90 self.note_class = note_class 91 if note_id is not None: 92 self.note_id = note_id 93 if citation is not None: 94 self.citation = citation 95 if body is not None: 96 self.note_body = body 97 98 @property 99 def citation(self) -> str: 100 note_citation = self.get_element("text:note-citation") 101 if note_citation: 102 return note_citation.text 103 return "" 104 105 @citation.setter 106 def citation(self, text: str | None) -> None: 107 note_citation = self.get_element("text:note-citation") 108 if note_citation: 109 note_citation.text = text # type:ignore 110 111 @property 112 def note_body(self) -> str: 113 note_body = self.get_element("text:note-body") 114 if note_body: 115 return note_body.text_content 116 return "" 117 118 @note_body.setter 119 def note_body(self, text_or_element: Element | str | None) -> None: 120 note_body = self.get_element("text:note-body") 121 if not note_body: 122 return None 123 if text_or_element is None: 124 note_body.text_content = "" 125 elif isinstance(text_or_element, str): 126 note_body.text_content = text_or_element 127 elif isinstance(text_or_element, Element): 128 note_body.clear() 129 note_body.append(text_or_element) 130 else: 131 raise TypeError(f'Unexpected type for body: "{type(text_or_element)}"') 132 133 def check_validity(self) -> None: 134 if not self.note_class: 135 raise ValueError('Note class must be "footnote" or "endnote"') 136 if not self.note_id: 137 raise ValueError("Note must have an id") 138 if not self.citation: 139 raise ValueError("Note must have a citation") 140 if not self.note_body: 141 pass
Either a footnote or a endnote element with the given text, optionally referencing it using the given note_id.
Arguments:
note_class -- 'footnote' or 'endnote'
note_id -- str
citation -- str
body -- str or Element
78 def __init__( 79 self, 80 note_class: str = "footnote", 81 note_id: str | None = None, 82 citation: str | None = None, 83 body: str | None = None, 84 **kwargs: Any, 85 ) -> None: 86 super().__init__(**kwargs) 87 if self._do_init: 88 self.insert(Element.from_tag("text:note-body"), position=0) 89 self.insert(Element.from_tag("text:note-citation"), position=0) 90 self.note_class = note_class 91 if note_id is not None: 92 self.note_id = note_id 93 if citation is not None: 94 self.citation = citation 95 if body is not None: 96 self.note_body = body
133 def check_validity(self) -> None: 134 if not self.note_class: 135 raise ValueError('Note class must be "footnote" or "endnote"') 136 if not self.note_id: 137 raise ValueError("Note must have an id") 138 if not self.citation: 139 raise ValueError("Note must have a citation") 140 if not self.note_body: 141 pass
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
831def PageBreak() -> Paragraph: 832 """Return an empty paragraph with a manual page break. 833 834 Using this function requires to register the page break style with: 835 document.add_page_break_style() 836 """ 837 return Paragraph("", style="odfdopagebreak")
Return an empty paragraph with a manual page break.
Using this function requires to register the page break style with: document.add_page_break_style()
147class Paragraph(ParagraphBase): 148 """Specialised element for paragraphs "text:p". The "text:p" element 149 represents a paragraph, which is the basic unit of text in an OpenDocument 150 file. 151 """ 152 153 _tag = "text:p" 154 155 def __init__( 156 self, 157 text_or_element: str | Element | None = None, 158 style: str | None = None, 159 **kwargs: Any, 160 ): 161 """Create a paragraph element of the given style containing the optional 162 given text. 163 164 Arguments: 165 166 text -- str or Element 167 168 style -- str 169 """ 170 super().__init__(**kwargs) 171 if self._do_init: 172 if isinstance(text_or_element, Element): 173 self.append(text_or_element) 174 else: 175 self.text = text_or_element # type:ignore 176 if style is not None: 177 self.style = style 178 179 def insert_note( 180 self, 181 note_element: Note | None = None, 182 after: str | Element | None = None, 183 note_class: str = "footnote", 184 note_id: str | None = None, 185 citation: str | None = None, 186 body: str | None = None, 187 ) -> None: 188 if note_element is None: 189 note_element = Note( 190 note_class=note_class, note_id=note_id, citation=citation, body=body 191 ) 192 else: 193 # XXX clone or modify the argument? 194 if note_class: 195 note_element.note_class = note_class 196 if note_id: 197 note_element.note_id = note_id 198 if citation: 199 note_element.citation = citation 200 if body: 201 note_element.note_body = body 202 note_element.check_validity() 203 if isinstance(after, str): 204 self._insert(note_element, after=after, main_text=True) 205 elif isinstance(after, Element): 206 after.insert(note_element, FIRST_CHILD) 207 else: 208 self.insert(note_element, FIRST_CHILD) 209 210 def insert_annotation( # noqa: C901 211 self, 212 annotation_element: Annotation | None = None, 213 before: str | None = None, 214 after: str | Element | None = None, 215 position: int | tuple = 0, 216 content: str | Element | None = None, 217 body: str | None = None, 218 creator: str | None = None, 219 date: datetime | None = None, 220 ) -> Annotation: 221 """Insert an annotation, at the position defined by the regex (before, 222 after, content) or by positionnal argument (position). If content is 223 provided, the annotation covers the full content regex. Else, the 224 annotation is positionned either 'before' or 'after' provided regex. 225 226 If content is an odf element (ie: paragraph, span, ...), the full inner 227 content is covered by the annotation (of the position just after if 228 content is a single empty tag). 229 230 If content/before or after exists (regex) and return a group of matching 231 positions, the position value is the index of matching place to use. 232 233 annotation_element can contain a previously created annotation, else 234 the annotation is created from the body, creator and optional date 235 (current date by default). 236 237 Arguments: 238 239 annotation_element -- Annotation or None 240 241 before -- str regular expression or None 242 243 after -- str regular expression or Element or None 244 245 content -- str regular expression or None, or Element 246 247 position -- int or tuple of int 248 249 body -- str or Element 250 251 creator -- str 252 253 date -- datetime 254 """ 255 256 if annotation_element is None: 257 annotation_element = Annotation( 258 text_or_element=body, creator=creator, date=date, parent=self 259 ) 260 else: 261 # XXX clone or modify the argument? 262 if body: 263 annotation_element.note_body = body 264 if creator: 265 annotation_element.dc_creator = creator 266 if date: 267 annotation_element.dc_date = date 268 annotation_element.check_validity() 269 270 # special case: content is an odf element (ie: a paragraph) 271 if isinstance(content, Element): 272 if content.is_empty(): 273 content.insert(annotation_element, xmlposition=NEXT_SIBLING) 274 return annotation_element 275 content.insert(annotation_element, start=True) 276 annotation_end = AnnotationEnd(annotation_element) 277 content.append(annotation_end) 278 return annotation_element 279 280 # special case 281 if isinstance(after, Element): 282 after.insert(annotation_element, FIRST_CHILD) 283 return annotation_element 284 285 # With "content" => automatically insert a "start" and an "end" 286 # bookmark 287 if ( 288 before is None 289 and after is None 290 and content is not None 291 and isinstance(position, int) 292 ): 293 # Start tag 294 self._insert( 295 annotation_element, before=content, position=position, main_text=True 296 ) 297 # End tag 298 annotation_end = AnnotationEnd(annotation_element) 299 self._insert( 300 annotation_end, after=content, position=position, main_text=True 301 ) 302 return annotation_element 303 304 # With "(int, int)" => automatically insert a "start" and an "end" 305 # bookmark 306 if ( 307 before is None 308 and after is None 309 and content is None 310 and isinstance(position, tuple) 311 ): 312 # Start 313 self._insert(annotation_element, position=position[0], main_text=True) 314 # End 315 annotation_end = AnnotationEnd(annotation_element) 316 self._insert(annotation_end, position=position[1], main_text=True) 317 return annotation_element 318 319 # Without "content" nor "position" 320 if content is not None or not isinstance(position, int): 321 raise ValueError("Bad arguments") 322 323 # Insert 324 self._insert( 325 annotation_element, 326 before=before, 327 after=after, 328 position=position, 329 main_text=True, 330 ) 331 return annotation_element 332 333 def insert_annotation_end( 334 self, 335 annotation_element: Annotation, 336 before: str | None = None, 337 after: str | None = None, 338 position: int = 0, 339 ) -> AnnotationEnd: 340 """Insert an annotation end tag for an existing annotation. If some end 341 tag already exists, replace it. Annotation end tag is set at the 342 position defined by the regex (before or after). 343 344 If content/before or after (regex) returns a group of matching 345 positions, the position value is the index of matching place to use. 346 347 Arguments: 348 349 annotation_element -- Annotation (mandatory) 350 351 before -- str regular expression or None 352 353 after -- str regular expression or None 354 355 position -- int 356 """ 357 358 if annotation_element is None: 359 raise ValueError 360 if not isinstance(annotation_element, Annotation): 361 raise TypeError("Not a <office:annotation> Annotation") 362 363 # remove existing end tag 364 name = annotation_element.name 365 existing_end_tag = self.get_annotation_end(name=name) 366 if existing_end_tag: 367 existing_end_tag.delete() 368 369 # create the end tag 370 end_tag = AnnotationEnd(annotation_element) 371 372 # Insert 373 self._insert( 374 end_tag, before=before, after=after, position=position, main_text=True 375 ) 376 return end_tag 377 378 def set_reference_mark( 379 self, 380 name: str, 381 before: str | None = None, 382 after: str | None = None, 383 position: int = 0, 384 content: str | Element | None = None, 385 ) -> Element: 386 """Insert a reference mark, at the position defined by the regex 387 (before, after, content) or by positionnal argument (position). If 388 content is provided, the annotation covers the full range content regex 389 (instances of ReferenceMarkStart and ReferenceMarkEnd are 390 created). Else, an instance of ReferenceMark is positionned either 391 'before' or 'after' provided regex. 392 393 If content is an ODF Element (ie: Paragraph, Span, ...), the full inner 394 content is referenced (of the position just after if content is a single 395 empty tag). 396 397 If content/before or after exists (regex) and return a group of matching 398 positions, the position value is the index of matching place to use. 399 400 Name is mandatory and shall be unique in the document for the preference 401 mark range. 402 403 Arguments: 404 405 name -- str 406 407 before -- str regular expression or None 408 409 after -- str regular expression or None, 410 411 content -- str regular expression or None, or Element 412 413 position -- int or tuple of int 414 415 Return: the created ReferenceMark or ReferenceMarkStart 416 """ 417 # special case: content is an odf element (ie: a paragraph) 418 if isinstance(content, Element): 419 if content.is_empty(): 420 reference = ReferenceMark(name) 421 content.insert(reference, xmlposition=NEXT_SIBLING) 422 return reference 423 reference_start = ReferenceMarkStart(name) 424 content.insert(reference_start, start=True) 425 reference_end = ReferenceMarkEnd(name) 426 content.append(reference_end) 427 return reference_start 428 429 # With "content" => automatically insert a "start" and an "end" 430 # reference 431 if ( 432 before is None 433 and after is None 434 and content is not None 435 and isinstance(position, int) 436 ): 437 # Start tag 438 reference_start = ReferenceMarkStart(name) 439 self._insert( 440 reference_start, before=content, position=position, main_text=True 441 ) 442 # End tag 443 reference_end = ReferenceMarkEnd(name) 444 self._insert( 445 reference_end, after=content, position=position, main_text=True 446 ) 447 return reference_start 448 449 # With "(int, int)" => automatically insert a "start" and an "end" 450 if ( 451 before is None 452 and after is None 453 and content is None 454 and isinstance(position, tuple) 455 ): 456 # Start 457 reference_start = ReferenceMarkStart(name) 458 self._insert(reference_start, position=position[0], main_text=True) 459 # End 460 reference_end = ReferenceMarkEnd(name) 461 self._insert(reference_end, position=position[1], main_text=True) 462 return reference_start 463 464 # Without "content" nor "position" 465 if content is not None or not isinstance(position, int): 466 raise ValueError("bad arguments") 467 468 # Insert a positional reference mark 469 reference = ReferenceMark(name) 470 self._insert( 471 reference, 472 before=before, 473 after=after, 474 position=position, 475 main_text=True, 476 ) 477 return reference 478 479 def set_reference_mark_end( 480 self, 481 reference_mark: Element, 482 before: str | None = None, 483 after: str | None = None, 484 position: int = 0, 485 ) -> ReferenceMarkEnd: 486 """Insert/move a ReferenceMarkEnd for an existing reference mark. If 487 some end tag already exists, replace it. Reference tag is set at the 488 position defined by the regex (before or after). 489 490 If content/before or after (regex) returns a group of matching 491 positions, the position value is the index of matching place to use. 492 493 Arguments: 494 495 reference_mark -- ReferenceMark or ReferenceMarkStart (mandatory) 496 497 before -- str regular expression or None 498 499 after -- str regular expression or None 500 501 position -- int 502 """ 503 if not isinstance(reference_mark, (ReferenceMark, ReferenceMarkStart)): 504 raise TypeError("Not a ReferenceMark or ReferenceMarkStart") 505 name = reference_mark.name 506 if isinstance(reference_mark, ReferenceMark): 507 # change it to a range reference: 508 reference_mark.tag = ReferenceMarkStart._tag 509 510 existing_end_tag = self.get_reference_mark_end(name=name) 511 if existing_end_tag: 512 existing_end_tag.delete() 513 514 # create the end tag 515 end_tag = ReferenceMarkEnd(name) 516 517 # Insert 518 self._insert( 519 end_tag, before=before, after=after, position=position, main_text=True 520 ) 521 return end_tag 522 523 def insert_variable(self, variable_element: Element, after: str | None) -> None: 524 self._insert(variable_element, after=after, main_text=True) 525 526 @_by_regex_offset 527 def set_span( 528 self, 529 match: str, 530 tail: str, 531 style: str, 532 regex: str | None = None, 533 offset: int | None = None, 534 length: int = 0, 535 ) -> Span: 536 """ 537 set_span(style, regex=None, offset=None, length=0) 538 Apply the given style to text content matching the regex OR the 539 positional arguments offset and length. 540 541 (match, tail: provided by regex decorator) 542 543 Arguments: 544 545 style -- str 546 547 regex -- str regular expression 548 549 offset -- int 550 551 length -- int 552 """ 553 span = Span(match, style=style) 554 span.tail = tail 555 return span 556 557 def remove_spans(self, keep_heading: bool = True) -> Element | list: 558 """Send back a copy of the element, without span styles. 559 If keep_heading is True (default), the first level heading style is left 560 unchanged. 561 """ 562 strip = (Span._tag,) 563 if keep_heading: 564 protect = ("text:h",) 565 else: 566 protect = None 567 return self.strip_tags(strip=strip, protect=protect) 568 569 def remove_span(self, spans: Element | list[Element]) -> Element | list: 570 """Send back a copy of the element, the spans (not a clone) removed. 571 572 Arguments: 573 574 spans -- Element or list of Element 575 """ 576 return self.strip_elements(spans) 577 578 @_by_regex_offset 579 def set_link( 580 self, 581 match: str, 582 tail: str, 583 url: str, 584 regex: str | None = None, 585 offset: int | None = None, 586 length: int = 0, 587 ) -> Element: 588 """ 589 set_link(url, regex=None, offset=None, length=0) 590 Make a link to the provided url from text content matching the regex 591 OR the positional arguments offset and length. 592 593 (match, tail: provided by regex decorator) 594 595 Arguments: 596 597 url -- str 598 599 regex -- str regular expression 600 601 offset -- int 602 603 length -- int 604 """ 605 link = Link(url, text=match) 606 link.tail = tail 607 return link 608 609 def remove_links(self) -> Element | list: 610 """Send back a copy of the element, without links tags.""" 611 strip = (Link._tag,) 612 return self.strip_tags(strip=strip) 613 614 def remove_link(self, links: Link | list[Link]) -> Element | list: 615 """Send back a copy of the element (not a clone), with the sub links 616 removed. 617 618 Arguments: 619 620 links -- Link or list of Link 621 """ 622 return self.strip_elements(links) 623 624 def insert_reference( 625 self, 626 name: str, 627 ref_format: str = "", 628 before: str | None = None, 629 after: str | Element | None = None, 630 position: int = 0, 631 display: str | None = None, 632 ) -> None: 633 """Create and insert a reference to a content marked by a reference 634 mark. The Reference element ("text:reference-ref") represents a 635 field that references a "text:reference-mark-start" or 636 "text:reference-mark" element. Its "text:reference-format" attribute 637 specifies what is displayed from the referenced element. Default is 638 'page'. Actual content is not automatically updated except for the 'text' 639 format. 640 641 name is mandatory and should represent an existing reference mark of the 642 document. 643 644 ref_format is the argument for format reference (default is 'page'). 645 646 The reference is inserted the position defined by the regex (before / 647 after), or by positionnal argument (position). If 'display' is provided, 648 it will be used as the text value for the reference. 649 650 If after is an ODF Element, the reference is inserted as first child of 651 this element. 652 653 Arguments: 654 655 name -- str 656 657 ref_format -- one of : 'chapter', 'direction', 'page', 'text', 658 'caption', 'category-and-value', 'value', 659 'number', 'number-all-superior', 660 'number-no-superior' 661 662 before -- str regular expression or None 663 664 after -- str regular expression or odf element or None 665 666 position -- int 667 668 display -- str or None 669 """ 670 reference = Reference(name, ref_format) 671 if display is None and ref_format == "text": 672 # get reference content 673 body = self.document_body 674 if not body: 675 body = self.root 676 mark = body.get_reference_mark(name=name) 677 if mark: 678 display = mark.referenced_text # type: ignore 679 if not display: 680 display = " " 681 reference.text = display 682 if isinstance(after, Element): 683 after.insert(reference, FIRST_CHILD) 684 else: 685 self._insert( 686 reference, before=before, after=after, position=position, main_text=True 687 ) 688 689 def set_bookmark( 690 self, 691 name: str, 692 before: str | None = None, 693 after: str | None = None, 694 position: int | tuple = 0, 695 role: str | None = None, 696 content: str | None = None, 697 ) -> Element | tuple[Element, Element]: 698 """Insert a bookmark before or after the characters in the text which 699 match the regex before/after. When the regex matches more of one part 700 of the text, position can be set to choose which part must be used. 701 If before and after are None, we use only position that is the number 702 of characters. 703 704 So, by default, this function inserts a bookmark before the first 705 character of the content. Role can be None, "start" or "end", we 706 insert respectively a position bookmark a bookmark-start or a 707 bookmark-end. 708 709 If content is not None these 2 calls are equivalent: 710 711 paragraph.set_bookmark("bookmark", content="xyz") 712 713 and: 714 715 paragraph.set_bookmark("bookmark", before="xyz", role="start") 716 paragraph.set_bookmark("bookmark", after="xyz", role="end") 717 718 719 If position is a 2-tuple, these 2 calls are equivalent: 720 721 paragraph.set_bookmark("bookmark", position=(10, 20)) 722 723 and: 724 725 paragraph.set_bookmark("bookmark", position=10, role="start") 726 paragraph.set_bookmark("bookmark", position=20, role="end") 727 728 729 Arguments: 730 731 name -- str 732 733 before -- str regex 734 735 after -- str regex 736 737 position -- int or (int, int) 738 739 role -- None, "start" or "end" 740 741 content -- str regex 742 """ 743 # With "content" => automatically insert a "start" and an "end" 744 # bookmark 745 if ( 746 before is None 747 and after is None 748 and role is None 749 and content is not None 750 and isinstance(position, int) 751 ): 752 # Start 753 start = BookmarkStart(name) 754 self._insert(start, before=content, position=position, main_text=True) 755 # End 756 end = BookmarkEnd(name) 757 self._insert(end, after=content, position=position, main_text=True) 758 return start, end 759 760 # With "(int, int)" => automatically insert a "start" and an "end" 761 # bookmark 762 if ( 763 before is None 764 and after is None 765 and role is None 766 and content is None 767 and isinstance(position, tuple) 768 ): 769 # Start 770 start = BookmarkStart(name) 771 self._insert(start, position=position[0], main_text=True) 772 # End 773 end = BookmarkEnd(name) 774 self._insert(end, position=position[1], main_text=True) 775 return start, end 776 777 # Without "content" nor "position" 778 if content is not None or not isinstance(position, int): 779 raise ValueError("bad arguments") 780 781 # Role 782 if role is None: 783 bookmark: Element = Bookmark(name) 784 elif role == "start": 785 bookmark = BookmarkStart(name) 786 elif role == "end": 787 bookmark = BookmarkEnd(name) 788 else: 789 raise ValueError("bad arguments") 790 791 # Insert 792 self._insert( 793 bookmark, before=before, after=after, position=position, main_text=True 794 ) 795 796 return bookmark
Specialised element for paragraphs "text:p". The "text:p" element represents a paragraph, which is the basic unit of text in an OpenDocument file.
155 def __init__( 156 self, 157 text_or_element: str | Element | None = None, 158 style: str | None = None, 159 **kwargs: Any, 160 ): 161 """Create a paragraph element of the given style containing the optional 162 given text. 163 164 Arguments: 165 166 text -- str or Element 167 168 style -- str 169 """ 170 super().__init__(**kwargs) 171 if self._do_init: 172 if isinstance(text_or_element, Element): 173 self.append(text_or_element) 174 else: 175 self.text = text_or_element # type:ignore 176 if style is not None: 177 self.style = style
Create a paragraph element of the given style containing the optional given text.
Arguments:
text -- str or Element
style -- str
179 def insert_note( 180 self, 181 note_element: Note | None = None, 182 after: str | Element | None = None, 183 note_class: str = "footnote", 184 note_id: str | None = None, 185 citation: str | None = None, 186 body: str | None = None, 187 ) -> None: 188 if note_element is None: 189 note_element = Note( 190 note_class=note_class, note_id=note_id, citation=citation, body=body 191 ) 192 else: 193 # XXX clone or modify the argument? 194 if note_class: 195 note_element.note_class = note_class 196 if note_id: 197 note_element.note_id = note_id 198 if citation: 199 note_element.citation = citation 200 if body: 201 note_element.note_body = body 202 note_element.check_validity() 203 if isinstance(after, str): 204 self._insert(note_element, after=after, main_text=True) 205 elif isinstance(after, Element): 206 after.insert(note_element, FIRST_CHILD) 207 else: 208 self.insert(note_element, FIRST_CHILD)
210 def insert_annotation( # noqa: C901 211 self, 212 annotation_element: Annotation | None = None, 213 before: str | None = None, 214 after: str | Element | None = None, 215 position: int | tuple = 0, 216 content: str | Element | None = None, 217 body: str | None = None, 218 creator: str | None = None, 219 date: datetime | None = None, 220 ) -> Annotation: 221 """Insert an annotation, at the position defined by the regex (before, 222 after, content) or by positionnal argument (position). If content is 223 provided, the annotation covers the full content regex. Else, the 224 annotation is positionned either 'before' or 'after' provided regex. 225 226 If content is an odf element (ie: paragraph, span, ...), the full inner 227 content is covered by the annotation (of the position just after if 228 content is a single empty tag). 229 230 If content/before or after exists (regex) and return a group of matching 231 positions, the position value is the index of matching place to use. 232 233 annotation_element can contain a previously created annotation, else 234 the annotation is created from the body, creator and optional date 235 (current date by default). 236 237 Arguments: 238 239 annotation_element -- Annotation or None 240 241 before -- str regular expression or None 242 243 after -- str regular expression or Element or None 244 245 content -- str regular expression or None, or Element 246 247 position -- int or tuple of int 248 249 body -- str or Element 250 251 creator -- str 252 253 date -- datetime 254 """ 255 256 if annotation_element is None: 257 annotation_element = Annotation( 258 text_or_element=body, creator=creator, date=date, parent=self 259 ) 260 else: 261 # XXX clone or modify the argument? 262 if body: 263 annotation_element.note_body = body 264 if creator: 265 annotation_element.dc_creator = creator 266 if date: 267 annotation_element.dc_date = date 268 annotation_element.check_validity() 269 270 # special case: content is an odf element (ie: a paragraph) 271 if isinstance(content, Element): 272 if content.is_empty(): 273 content.insert(annotation_element, xmlposition=NEXT_SIBLING) 274 return annotation_element 275 content.insert(annotation_element, start=True) 276 annotation_end = AnnotationEnd(annotation_element) 277 content.append(annotation_end) 278 return annotation_element 279 280 # special case 281 if isinstance(after, Element): 282 after.insert(annotation_element, FIRST_CHILD) 283 return annotation_element 284 285 # With "content" => automatically insert a "start" and an "end" 286 # bookmark 287 if ( 288 before is None 289 and after is None 290 and content is not None 291 and isinstance(position, int) 292 ): 293 # Start tag 294 self._insert( 295 annotation_element, before=content, position=position, main_text=True 296 ) 297 # End tag 298 annotation_end = AnnotationEnd(annotation_element) 299 self._insert( 300 annotation_end, after=content, position=position, main_text=True 301 ) 302 return annotation_element 303 304 # With "(int, int)" => automatically insert a "start" and an "end" 305 # bookmark 306 if ( 307 before is None 308 and after is None 309 and content is None 310 and isinstance(position, tuple) 311 ): 312 # Start 313 self._insert(annotation_element, position=position[0], main_text=True) 314 # End 315 annotation_end = AnnotationEnd(annotation_element) 316 self._insert(annotation_end, position=position[1], main_text=True) 317 return annotation_element 318 319 # Without "content" nor "position" 320 if content is not None or not isinstance(position, int): 321 raise ValueError("Bad arguments") 322 323 # Insert 324 self._insert( 325 annotation_element, 326 before=before, 327 after=after, 328 position=position, 329 main_text=True, 330 ) 331 return annotation_element
Insert an annotation, at the position defined by the regex (before, after, content) or by positionnal argument (position). If content is provided, the annotation covers the full content regex. Else, the annotation is positionned either 'before' or 'after' provided regex.
If content is an odf element (ie: paragraph, span, ...), the full inner content is covered by the annotation (of the position just after if content is a single empty tag).
If content/before or after exists (regex) and return a group of matching positions, the position value is the index of matching place to use.
annotation_element can contain a previously created annotation, else the annotation is created from the body, creator and optional date (current date by default).
Arguments:
annotation_element -- Annotation or None
before -- str regular expression or None
after -- str regular expression or Element or None
content -- str regular expression or None, or Element
position -- int or tuple of int
body -- str or Element
creator -- str
date -- datetime
333 def insert_annotation_end( 334 self, 335 annotation_element: Annotation, 336 before: str | None = None, 337 after: str | None = None, 338 position: int = 0, 339 ) -> AnnotationEnd: 340 """Insert an annotation end tag for an existing annotation. If some end 341 tag already exists, replace it. Annotation end tag is set at the 342 position defined by the regex (before or after). 343 344 If content/before or after (regex) returns a group of matching 345 positions, the position value is the index of matching place to use. 346 347 Arguments: 348 349 annotation_element -- Annotation (mandatory) 350 351 before -- str regular expression or None 352 353 after -- str regular expression or None 354 355 position -- int 356 """ 357 358 if annotation_element is None: 359 raise ValueError 360 if not isinstance(annotation_element, Annotation): 361 raise TypeError("Not a <office:annotation> Annotation") 362 363 # remove existing end tag 364 name = annotation_element.name 365 existing_end_tag = self.get_annotation_end(name=name) 366 if existing_end_tag: 367 existing_end_tag.delete() 368 369 # create the end tag 370 end_tag = AnnotationEnd(annotation_element) 371 372 # Insert 373 self._insert( 374 end_tag, before=before, after=after, position=position, main_text=True 375 ) 376 return end_tag
Insert an annotation end tag for an existing annotation. If some end tag already exists, replace it. Annotation end tag is set at the position defined by the regex (before or after).
If content/before or after (regex) returns a group of matching positions, the position value is the index of matching place to use.
Arguments:
annotation_element -- Annotation (mandatory)
before -- str regular expression or None
after -- str regular expression or None
position -- int
378 def set_reference_mark( 379 self, 380 name: str, 381 before: str | None = None, 382 after: str | None = None, 383 position: int = 0, 384 content: str | Element | None = None, 385 ) -> Element: 386 """Insert a reference mark, at the position defined by the regex 387 (before, after, content) or by positionnal argument (position). If 388 content is provided, the annotation covers the full range content regex 389 (instances of ReferenceMarkStart and ReferenceMarkEnd are 390 created). Else, an instance of ReferenceMark is positionned either 391 'before' or 'after' provided regex. 392 393 If content is an ODF Element (ie: Paragraph, Span, ...), the full inner 394 content is referenced (of the position just after if content is a single 395 empty tag). 396 397 If content/before or after exists (regex) and return a group of matching 398 positions, the position value is the index of matching place to use. 399 400 Name is mandatory and shall be unique in the document for the preference 401 mark range. 402 403 Arguments: 404 405 name -- str 406 407 before -- str regular expression or None 408 409 after -- str regular expression or None, 410 411 content -- str regular expression or None, or Element 412 413 position -- int or tuple of int 414 415 Return: the created ReferenceMark or ReferenceMarkStart 416 """ 417 # special case: content is an odf element (ie: a paragraph) 418 if isinstance(content, Element): 419 if content.is_empty(): 420 reference = ReferenceMark(name) 421 content.insert(reference, xmlposition=NEXT_SIBLING) 422 return reference 423 reference_start = ReferenceMarkStart(name) 424 content.insert(reference_start, start=True) 425 reference_end = ReferenceMarkEnd(name) 426 content.append(reference_end) 427 return reference_start 428 429 # With "content" => automatically insert a "start" and an "end" 430 # reference 431 if ( 432 before is None 433 and after is None 434 and content is not None 435 and isinstance(position, int) 436 ): 437 # Start tag 438 reference_start = ReferenceMarkStart(name) 439 self._insert( 440 reference_start, before=content, position=position, main_text=True 441 ) 442 # End tag 443 reference_end = ReferenceMarkEnd(name) 444 self._insert( 445 reference_end, after=content, position=position, main_text=True 446 ) 447 return reference_start 448 449 # With "(int, int)" => automatically insert a "start" and an "end" 450 if ( 451 before is None 452 and after is None 453 and content is None 454 and isinstance(position, tuple) 455 ): 456 # Start 457 reference_start = ReferenceMarkStart(name) 458 self._insert(reference_start, position=position[0], main_text=True) 459 # End 460 reference_end = ReferenceMarkEnd(name) 461 self._insert(reference_end, position=position[1], main_text=True) 462 return reference_start 463 464 # Without "content" nor "position" 465 if content is not None or not isinstance(position, int): 466 raise ValueError("bad arguments") 467 468 # Insert a positional reference mark 469 reference = ReferenceMark(name) 470 self._insert( 471 reference, 472 before=before, 473 after=after, 474 position=position, 475 main_text=True, 476 ) 477 return reference
Insert a reference mark, at the position defined by the regex (before, after, content) or by positionnal argument (position). If content is provided, the annotation covers the full range content regex (instances of ReferenceMarkStart and ReferenceMarkEnd are created). Else, an instance of ReferenceMark is positionned either 'before' or 'after' provided regex.
If content is an ODF Element (ie: Paragraph, Span, ...), the full inner content is referenced (of the position just after if content is a single empty tag).
If content/before or after exists (regex) and return a group of matching positions, the position value is the index of matching place to use.
Name is mandatory and shall be unique in the document for the preference mark range.
Arguments:
name -- str
before -- str regular expression or None
after -- str regular expression or None,
content -- str regular expression or None, or Element
position -- int or tuple of int
Return: the created ReferenceMark or ReferenceMarkStart
479 def set_reference_mark_end( 480 self, 481 reference_mark: Element, 482 before: str | None = None, 483 after: str | None = None, 484 position: int = 0, 485 ) -> ReferenceMarkEnd: 486 """Insert/move a ReferenceMarkEnd for an existing reference mark. If 487 some end tag already exists, replace it. Reference tag is set at the 488 position defined by the regex (before or after). 489 490 If content/before or after (regex) returns a group of matching 491 positions, the position value is the index of matching place to use. 492 493 Arguments: 494 495 reference_mark -- ReferenceMark or ReferenceMarkStart (mandatory) 496 497 before -- str regular expression or None 498 499 after -- str regular expression or None 500 501 position -- int 502 """ 503 if not isinstance(reference_mark, (ReferenceMark, ReferenceMarkStart)): 504 raise TypeError("Not a ReferenceMark or ReferenceMarkStart") 505 name = reference_mark.name 506 if isinstance(reference_mark, ReferenceMark): 507 # change it to a range reference: 508 reference_mark.tag = ReferenceMarkStart._tag 509 510 existing_end_tag = self.get_reference_mark_end(name=name) 511 if existing_end_tag: 512 existing_end_tag.delete() 513 514 # create the end tag 515 end_tag = ReferenceMarkEnd(name) 516 517 # Insert 518 self._insert( 519 end_tag, before=before, after=after, position=position, main_text=True 520 ) 521 return end_tag
Insert/move a ReferenceMarkEnd for an existing reference mark. If some end tag already exists, replace it. Reference tag is set at the position defined by the regex (before or after).
If content/before or after (regex) returns a group of matching positions, the position value is the index of matching place to use.
Arguments:
reference_mark -- ReferenceMark or ReferenceMarkStart (mandatory)
before -- str regular expression or None
after -- str regular expression or None
position -- int
526 @_by_regex_offset 527 def set_span( 528 self, 529 match: str, 530 tail: str, 531 style: str, 532 regex: str | None = None, 533 offset: int | None = None, 534 length: int = 0, 535 ) -> Span: 536 """ 537 set_span(style, regex=None, offset=None, length=0) 538 Apply the given style to text content matching the regex OR the 539 positional arguments offset and length. 540 541 (match, tail: provided by regex decorator) 542 543 Arguments: 544 545 style -- str 546 547 regex -- str regular expression 548 549 offset -- int 550 551 length -- int 552 """ 553 span = Span(match, style=style) 554 span.tail = tail 555 return span
set_span(style, regex=None, offset=None, length=0) Apply the given style to text content matching the regex OR the positional arguments offset and length.
(match, tail: provided by regex decorator)
Arguments:
style -- str
regex -- str regular expression
offset -- int
length -- int
557 def remove_spans(self, keep_heading: bool = True) -> Element | list: 558 """Send back a copy of the element, without span styles. 559 If keep_heading is True (default), the first level heading style is left 560 unchanged. 561 """ 562 strip = (Span._tag,) 563 if keep_heading: 564 protect = ("text:h",) 565 else: 566 protect = None 567 return self.strip_tags(strip=strip, protect=protect)
Send back a copy of the element, without span styles. If keep_heading is True (default), the first level heading style is left unchanged.
569 def remove_span(self, spans: Element | list[Element]) -> Element | list: 570 """Send back a copy of the element, the spans (not a clone) removed. 571 572 Arguments: 573 574 spans -- Element or list of Element 575 """ 576 return self.strip_elements(spans)
Send back a copy of the element, the spans (not a clone) removed.
Arguments:
spans -- Element or list of Element
578 @_by_regex_offset 579 def set_link( 580 self, 581 match: str, 582 tail: str, 583 url: str, 584 regex: str | None = None, 585 offset: int | None = None, 586 length: int = 0, 587 ) -> Element: 588 """ 589 set_link(url, regex=None, offset=None, length=0) 590 Make a link to the provided url from text content matching the regex 591 OR the positional arguments offset and length. 592 593 (match, tail: provided by regex decorator) 594 595 Arguments: 596 597 url -- str 598 599 regex -- str regular expression 600 601 offset -- int 602 603 length -- int 604 """ 605 link = Link(url, text=match) 606 link.tail = tail 607 return link
set_link(url, regex=None, offset=None, length=0) Make a link to the provided url from text content matching the regex OR the positional arguments offset and length.
(match, tail: provided by regex decorator)
Arguments:
url -- str
regex -- str regular expression
offset -- int
length -- int
609 def remove_links(self) -> Element | list: 610 """Send back a copy of the element, without links tags.""" 611 strip = (Link._tag,) 612 return self.strip_tags(strip=strip)
Send back a copy of the element, without links tags.
614 def remove_link(self, links: Link | list[Link]) -> Element | list: 615 """Send back a copy of the element (not a clone), with the sub links 616 removed. 617 618 Arguments: 619 620 links -- Link or list of Link 621 """ 622 return self.strip_elements(links)
Send back a copy of the element (not a clone), with the sub links removed.
Arguments:
links -- Link or list of Link
624 def insert_reference( 625 self, 626 name: str, 627 ref_format: str = "", 628 before: str | None = None, 629 after: str | Element | None = None, 630 position: int = 0, 631 display: str | None = None, 632 ) -> None: 633 """Create and insert a reference to a content marked by a reference 634 mark. The Reference element ("text:reference-ref") represents a 635 field that references a "text:reference-mark-start" or 636 "text:reference-mark" element. Its "text:reference-format" attribute 637 specifies what is displayed from the referenced element. Default is 638 'page'. Actual content is not automatically updated except for the 'text' 639 format. 640 641 name is mandatory and should represent an existing reference mark of the 642 document. 643 644 ref_format is the argument for format reference (default is 'page'). 645 646 The reference is inserted the position defined by the regex (before / 647 after), or by positionnal argument (position). If 'display' is provided, 648 it will be used as the text value for the reference. 649 650 If after is an ODF Element, the reference is inserted as first child of 651 this element. 652 653 Arguments: 654 655 name -- str 656 657 ref_format -- one of : 'chapter', 'direction', 'page', 'text', 658 'caption', 'category-and-value', 'value', 659 'number', 'number-all-superior', 660 'number-no-superior' 661 662 before -- str regular expression or None 663 664 after -- str regular expression or odf element or None 665 666 position -- int 667 668 display -- str or None 669 """ 670 reference = Reference(name, ref_format) 671 if display is None and ref_format == "text": 672 # get reference content 673 body = self.document_body 674 if not body: 675 body = self.root 676 mark = body.get_reference_mark(name=name) 677 if mark: 678 display = mark.referenced_text # type: ignore 679 if not display: 680 display = " " 681 reference.text = display 682 if isinstance(after, Element): 683 after.insert(reference, FIRST_CHILD) 684 else: 685 self._insert( 686 reference, before=before, after=after, position=position, main_text=True 687 )
Create and insert a reference to a content marked by a reference mark. The Reference element ("text:reference-ref") represents a field that references a "text:reference-mark-start" or "text:reference-mark" element. Its "text:reference-format" attribute specifies what is displayed from the referenced element. Default is 'page'. Actual content is not automatically updated except for the 'text' format.
name is mandatory and should represent an existing reference mark of the document.
ref_format is the argument for format reference (default is 'page').
The reference is inserted the position defined by the regex (before / after), or by positionnal argument (position). If 'display' is provided, it will be used as the text value for the reference.
If after is an ODF Element, the reference is inserted as first child of this element.
Arguments:
name -- str
ref_format -- one of : 'chapter', 'direction', 'page', 'text',
'caption', 'category-and-value', 'value',
'number', 'number-all-superior',
'number-no-superior'
before -- str regular expression or None
after -- str regular expression or odf element or None
position -- int
display -- str or None
689 def set_bookmark( 690 self, 691 name: str, 692 before: str | None = None, 693 after: str | None = None, 694 position: int | tuple = 0, 695 role: str | None = None, 696 content: str | None = None, 697 ) -> Element | tuple[Element, Element]: 698 """Insert a bookmark before or after the characters in the text which 699 match the regex before/after. When the regex matches more of one part 700 of the text, position can be set to choose which part must be used. 701 If before and after are None, we use only position that is the number 702 of characters. 703 704 So, by default, this function inserts a bookmark before the first 705 character of the content. Role can be None, "start" or "end", we 706 insert respectively a position bookmark a bookmark-start or a 707 bookmark-end. 708 709 If content is not None these 2 calls are equivalent: 710 711 paragraph.set_bookmark("bookmark", content="xyz") 712 713 and: 714 715 paragraph.set_bookmark("bookmark", before="xyz", role="start") 716 paragraph.set_bookmark("bookmark", after="xyz", role="end") 717 718 719 If position is a 2-tuple, these 2 calls are equivalent: 720 721 paragraph.set_bookmark("bookmark", position=(10, 20)) 722 723 and: 724 725 paragraph.set_bookmark("bookmark", position=10, role="start") 726 paragraph.set_bookmark("bookmark", position=20, role="end") 727 728 729 Arguments: 730 731 name -- str 732 733 before -- str regex 734 735 after -- str regex 736 737 position -- int or (int, int) 738 739 role -- None, "start" or "end" 740 741 content -- str regex 742 """ 743 # With "content" => automatically insert a "start" and an "end" 744 # bookmark 745 if ( 746 before is None 747 and after is None 748 and role is None 749 and content is not None 750 and isinstance(position, int) 751 ): 752 # Start 753 start = BookmarkStart(name) 754 self._insert(start, before=content, position=position, main_text=True) 755 # End 756 end = BookmarkEnd(name) 757 self._insert(end, after=content, position=position, main_text=True) 758 return start, end 759 760 # With "(int, int)" => automatically insert a "start" and an "end" 761 # bookmark 762 if ( 763 before is None 764 and after is None 765 and role is None 766 and content is None 767 and isinstance(position, tuple) 768 ): 769 # Start 770 start = BookmarkStart(name) 771 self._insert(start, position=position[0], main_text=True) 772 # End 773 end = BookmarkEnd(name) 774 self._insert(end, position=position[1], main_text=True) 775 return start, end 776 777 # Without "content" nor "position" 778 if content is not None or not isinstance(position, int): 779 raise ValueError("bad arguments") 780 781 # Role 782 if role is None: 783 bookmark: Element = Bookmark(name) 784 elif role == "start": 785 bookmark = BookmarkStart(name) 786 elif role == "end": 787 bookmark = BookmarkEnd(name) 788 else: 789 raise ValueError("bad arguments") 790 791 # Insert 792 self._insert( 793 bookmark, before=before, after=after, position=position, main_text=True 794 ) 795 796 return bookmark
Insert a bookmark before or after the characters in the text which match the regex before/after. When the regex matches more of one part of the text, position can be set to choose which part must be used. If before and after are None, we use only position that is the number of characters.
So, by default, this function inserts a bookmark before the first character of the content. Role can be None, "start" or "end", we insert respectively a position bookmark a bookmark-start or a bookmark-end.
If content is not None these 2 calls are equivalent:
paragraph.set_bookmark("bookmark", content="xyz")
and:
paragraph.set_bookmark("bookmark", before="xyz", role="start") paragraph.set_bookmark("bookmark", after="xyz", role="end")
If position is a 2-tuple, these 2 calls are equivalent:
paragraph.set_bookmark("bookmark", position=(10, 20))
and:
paragraph.set_bookmark("bookmark", position=10, role="start") paragraph.set_bookmark("bookmark", position=20, role="end")
Arguments:
name -- str
before -- str regex
after -- str regex
position -- int or (int, int)
role -- None, "start" or "end"
content -- str regex
Inherited Members
- odfdo.paragraph_base.ParagraphBase
- get_formatted_text
- append_plain_text
- style
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
146class RectangleShape(ShapeBase): 147 """Create a rectangle shape. 148 149 Arguments: 150 151 style -- str 152 153 text_style -- str 154 155 draw_id -- str 156 157 layer -- str 158 159 position -- (str, str) 160 161 size -- (str, str) 162 163 """ 164 165 _tag = "draw:rect" 166 _properties: tuple[PropDef, ...] = () 167 168 def __init__( 169 self, 170 style: str | None = None, 171 text_style: str | None = None, 172 draw_id: str | None = None, 173 layer: str | None = None, 174 position: tuple | None = None, 175 size: tuple | None = None, 176 **kwargs: Any, 177 ) -> None: 178 kwargs.update( 179 { 180 "style": style, 181 "text_style": text_style, 182 "draw_id": draw_id, 183 "layer": layer, 184 "size": size, 185 "position": position, 186 } 187 ) 188 super().__init__(**kwargs)
Create a rectangle shape.
Arguments:
style -- str
text_style -- str
draw_id -- str
layer -- str
position -- (str, str)
size -- (str, str)
168 def __init__( 169 self, 170 style: str | None = None, 171 text_style: str | None = None, 172 draw_id: str | None = None, 173 layer: str | None = None, 174 position: tuple | None = None, 175 size: tuple | None = None, 176 **kwargs: Any, 177 ) -> None: 178 kwargs.update( 179 { 180 "style": style, 181 "text_style": text_style, 182 "draw_id": draw_id, 183 "layer": layer, 184 "size": size, 185 "position": position, 186 } 187 ) 188 super().__init__(**kwargs)
Inherited Members
- odfdo.shapes.ShapeBase
- get_formatted_text
- draw_id
- layer
- width
- height
- pos_x
- pos_y
- presentation_class
- style
- text_style
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
- odfdo.frame.SizeMix
- size
- odfdo.frame.PosMix
- position
59class Reference(Element): 60 """A reference to a content marked by a reference mark. 61 The odf_reference element ("text:reference-ref") represents a field that 62 references a "text:reference-mark-start" or "text:reference-mark" element. 63 Its text:reference-format attribute specifies what is displayed from the 64 referenced element. Default is 'page' 65 Actual content is not updated except for the 'text' format by the 66 update() method. 67 68 69 Creation of references can be tricky, consider using this method: 70 odfdo.paragraph.insert_reference() 71 72 Values for text:reference-format : 73 The defined values for the text:reference-format attribute supported by 74 all reference fields are: 75 - 'chapter': displays the number of the chapter in which the 76 referenced item appears. 77 - 'direction': displays whether the referenced item is above or 78 below the reference field. 79 - 'page': displays the number of the page on which the referenced 80 item appears. 81 - 'text': displays the text of the referenced item. 82 Additional defined values for the text:reference-format attribute 83 supported by references to sequence fields are: 84 - 'caption': displays the caption in which the sequence is used. 85 - 'category-and-value': displays the name and value of the sequence. 86 - 'value': displays the value of the sequence. 87 88 References to bookmarks and other references support additional values, 89 which display the list label of the referenced item. If the referenced 90 item is contained in a list or a numbered paragraph, the list label is 91 the formatted number of the paragraph which contains the referenced 92 item. If the referenced item is not contained in a list or numbered 93 paragraph, the list label is empty, and the referenced field therefore 94 displays nothing. If the referenced bookmark or reference contains more 95 than one paragraph, the list label of the paragraph at which the 96 bookmark or reference starts is taken. 97 98 Additional defined values for the text:reference-format attribute 99 supported by all references to bookmark's or other reference fields 100 are: 101 - 'number': displays the list label of the referenced item. [...] 102 - 'number-all-superior': displays the list label of the referenced 103 item and adds the contents of all list labels of superior levels 104 in front of it. [...] 105 - 'number-no-superior': displays the contents of the list label of 106 the referenced item. 107 """ 108 109 _tag = "text:reference-ref" 110 _properties = (PropDef("name", "text:ref-name"),) 111 format_allowed = ( 112 "chapter", 113 "direction", 114 "page", 115 "text", 116 "caption", 117 "category-and-value", 118 "value", 119 "number", 120 "number-all-superior", 121 "number-no-superior", 122 ) 123 124 def __init__(self, name: str = "", ref_format: str = "", **kwargs: Any) -> None: 125 """Create a reference to a content marked by a reference mark. An 126 actual reference mark with the provided name should exist. 127 128 Consider using: odfdo.paragraph.insert_reference() 129 130 The text:ref-name attribute identifies a "text:reference-mark" or 131 "text:referencemark-start" element by the value of that element's 132 text:name attribute. 133 If ref_format is 'text', the current text content of the reference_mark 134 is retrieved. 135 136 Arguments: 137 138 name -- str : name of the reference mark 139 140 ref_format -- str : format of the field. Default is 'page', allowed 141 values are 'chapter', 'direction', 'page', 'text', 142 'caption', 'category-and-value', 'value', 'number', 143 'number-all-superior', 'number-no-superior'. 144 """ 145 super().__init__(**kwargs) 146 if self._do_init: 147 self.name = name 148 self.ref_format = ref_format 149 150 @property 151 def ref_format(self) -> str | None: 152 reference = self.get_attribute("text:reference-format") 153 if isinstance(reference, str): 154 return reference 155 return None 156 157 @ref_format.setter 158 def ref_format(self, ref_format: str) -> None: 159 """Set the text:reference-format attribute. 160 161 Arguments: 162 163 ref_format -- str 164 """ 165 if not ref_format or ref_format not in self.format_allowed: 166 ref_format = "page" 167 self.set_attribute("text:reference-format", ref_format) 168 169 def update(self) -> None: 170 """Update the content of the reference text field. Currently only 171 'text' format is implemented. Other values, for example the 'page' text 172 field, may need to be refreshed through a visual ODF parser. 173 """ 174 ref_format = self.ref_format 175 if ref_format != "text": 176 # only 'text' is implemented 177 return None 178 body = self.document_body 179 if not body: 180 body = self.root 181 name = self.name 182 reference = body.get_reference_mark(name=name) 183 if not reference: 184 return None 185 # we know it is a ReferenceMarkStart: 186 self.text = reference.referenced_text() # type: ignore
A reference to a content marked by a reference mark. The odf_reference element ("text:reference-ref") represents a field that references a "text:reference-mark-start" or "text:reference-mark" element. Its text:reference-format attribute specifies what is displayed from the referenced element. Default is 'page' Actual content is not updated except for the 'text' format by the update() method.
Creation of references can be tricky, consider using this method: odfdo.paragraph.insert_reference()
Values for text:reference-format : The defined values for the text:reference-format attribute supported by all reference fields are: - 'chapter': displays the number of the chapter in which the referenced item appears. - 'direction': displays whether the referenced item is above or below the reference field. - 'page': displays the number of the page on which the referenced item appears. - 'text': displays the text of the referenced item. Additional defined values for the text:reference-format attribute supported by references to sequence fields are: - 'caption': displays the caption in which the sequence is used. - 'category-and-value': displays the name and value of the sequence. - 'value': displays the value of the sequence.
References to bookmarks and other references support additional values,
which display the list label of the referenced item. If the referenced
item is contained in a list or a numbered paragraph, the list label is
the formatted number of the paragraph which contains the referenced
item. If the referenced item is not contained in a list or numbered
paragraph, the list label is empty, and the referenced field therefore
displays nothing. If the referenced bookmark or reference contains more
than one paragraph, the list label of the paragraph at which the
bookmark or reference starts is taken.
Additional defined values for the text:reference-format attribute
supported by all references to bookmark's or other reference fields
are:
- 'number': displays the list label of the referenced item. [...]
- 'number-all-superior': displays the list label of the referenced
item and adds the contents of all list labels of superior levels
in front of it. [...]
- 'number-no-superior': displays the contents of the list label of
the referenced item.
124 def __init__(self, name: str = "", ref_format: str = "", **kwargs: Any) -> None: 125 """Create a reference to a content marked by a reference mark. An 126 actual reference mark with the provided name should exist. 127 128 Consider using: odfdo.paragraph.insert_reference() 129 130 The text:ref-name attribute identifies a "text:reference-mark" or 131 "text:referencemark-start" element by the value of that element's 132 text:name attribute. 133 If ref_format is 'text', the current text content of the reference_mark 134 is retrieved. 135 136 Arguments: 137 138 name -- str : name of the reference mark 139 140 ref_format -- str : format of the field. Default is 'page', allowed 141 values are 'chapter', 'direction', 'page', 'text', 142 'caption', 'category-and-value', 'value', 'number', 143 'number-all-superior', 'number-no-superior'. 144 """ 145 super().__init__(**kwargs) 146 if self._do_init: 147 self.name = name 148 self.ref_format = ref_format
Create a reference to a content marked by a reference mark. An actual reference mark with the provided name should exist.
Consider using: odfdo.paragraph.insert_reference()
The text:ref-name attribute identifies a "text:reference-mark" or "text:referencemark-start" element by the value of that element's text:name attribute. If ref_format is 'text', the current text content of the reference_mark is retrieved.
Arguments:
name -- str : name of the reference mark
ref_format -- str : format of the field. Default is 'page', allowed
values are 'chapter', 'direction', 'page', 'text',
'caption', 'category-and-value', 'value', 'number',
'number-all-superior', 'number-no-superior'.
150 @property 151 def ref_format(self) -> str | None: 152 reference = self.get_attribute("text:reference-format") 153 if isinstance(reference, str): 154 return reference 155 return None
Set the text:reference-format attribute.
Arguments:
ref_format -- str
169 def update(self) -> None: 170 """Update the content of the reference text field. Currently only 171 'text' format is implemented. Other values, for example the 'page' text 172 field, may need to be refreshed through a visual ODF parser. 173 """ 174 ref_format = self.ref_format 175 if ref_format != "text": 176 # only 'text' is implemented 177 return None 178 body = self.document_body 179 if not body: 180 body = self.root 181 name = self.name 182 reference = body.get_reference_mark(name=name) 183 if not reference: 184 return None 185 # we know it is a ReferenceMarkStart: 186 self.text = reference.referenced_text() # type: ignore
Update the content of the reference text field. Currently only 'text' format is implemented. Other values, for example the 'page' text field, may need to be refreshed through a visual ODF parser.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
192class ReferenceMark(Element): 193 """A point reference. 194 A point reference marks a position in text and is represented by a single 195 "text:reference-mark" element. 196 """ 197 198 _tag = "text:reference-mark" 199 _properties = (PropDef("name", "text:name"),) 200 201 def __init__(self, name: str = "", **kwargs: Any) -> None: 202 """A point reference. A point reference marks a position in text and is 203 represented by a single "text:reference-mark" element. 204 Consider using the wrapper: odfdo.paragraph.set_reference_mark() 205 206 Arguments: 207 208 name -- str 209 """ 210 super().__init__(**kwargs) 211 if self._do_init: 212 self.name = name
A point reference. A point reference marks a position in text and is represented by a single "text:reference-mark" element.
201 def __init__(self, name: str = "", **kwargs: Any) -> None: 202 """A point reference. A point reference marks a position in text and is 203 represented by a single "text:reference-mark" element. 204 Consider using the wrapper: odfdo.paragraph.set_reference_mark() 205 206 Arguments: 207 208 name -- str 209 """ 210 super().__init__(**kwargs) 211 if self._do_init: 212 self.name = name
A point reference. A point reference marks a position in text and is represented by a single "text:reference-mark" element. Consider using the wrapper: odfdo.paragraph.set_reference_mark()
Arguments:
name -- str
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
218class ReferenceMarkEnd(Element): 219 """The "text:reference-mark-end" element represents the end of a range 220 reference. 221 """ 222 223 _tag = "text:reference-mark-end" 224 _properties = (PropDef("name", "text:name"),) 225 226 def __init__(self, name: str = "", **kwargs: Any) -> None: 227 """The "text:reference-mark-end" element represent the end of a range 228 reference. 229 Consider using the wrappers: odfdo.paragraph.set_reference_mark() and 230 odfdo.paragraph.set_reference_mark_end() 231 232 Arguments: 233 234 name -- str 235 """ 236 super().__init__(**kwargs) 237 if self._do_init: 238 self.name = name 239 240 def referenced_text(self) -> str: 241 """Return the text between reference-mark-start and reference-mark-end.""" 242 name = self.name 243 request = ( 244 f"//text()" 245 f"[preceding::text:reference-mark-start[@text:name='{name}'] " 246 f"and following::text:reference-mark-end[@text:name='{name}']]" 247 ) 248 result = " ".join(str(x) for x in self.xpath(request)) 249 return result
The "text:reference-mark-end" element represents the end of a range reference.
226 def __init__(self, name: str = "", **kwargs: Any) -> None: 227 """The "text:reference-mark-end" element represent the end of a range 228 reference. 229 Consider using the wrappers: odfdo.paragraph.set_reference_mark() and 230 odfdo.paragraph.set_reference_mark_end() 231 232 Arguments: 233 234 name -- str 235 """ 236 super().__init__(**kwargs) 237 if self._do_init: 238 self.name = name
The "text:reference-mark-end" element represent the end of a range reference. Consider using the wrappers: odfdo.paragraph.set_reference_mark() and odfdo.paragraph.set_reference_mark_end()
Arguments:
name -- str
240 def referenced_text(self) -> str: 241 """Return the text between reference-mark-start and reference-mark-end.""" 242 name = self.name 243 request = ( 244 f"//text()" 245 f"[preceding::text:reference-mark-start[@text:name='{name}'] " 246 f"and following::text:reference-mark-end[@text:name='{name}']]" 247 ) 248 result = " ".join(str(x) for x in self.xpath(request)) 249 return result
Return the text between reference-mark-start and reference-mark-end.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
255class ReferenceMarkStart(Element): 256 """The "text:reference-mark-start" element represents the start of a 257 range reference. 258 """ 259 260 _tag = "text:reference-mark-start" 261 _properties = (PropDef("name", "text:name"),) 262 263 def __init__(self, name: str = "", **kwargs: Any) -> None: 264 """The "text:reference-mark-start" element represent the start of a range 265 reference. 266 Consider using the wrapper: odfdo.paragraph.set_reference_mark() 267 268 Arguments: 269 270 name -- str 271 """ 272 super().__init__(**kwargs) 273 if self._do_init: 274 self.name = name 275 276 def referenced_text(self) -> str: 277 """Return the text between reference-mark-start and reference-mark-end.""" 278 name = self.name 279 request = ( 280 f"//text()" 281 f"[preceding::text:reference-mark-start[@text:name='{name}'] " 282 f"and following::text:reference-mark-end[@text:name='{name}']]" 283 ) 284 result = " ".join(str(x) for x in self.xpath(request)) 285 return result 286 287 def get_referenced( 288 self, 289 no_header: bool = False, 290 clean: bool = True, 291 as_xml: bool = False, 292 as_list: bool = False, 293 ) -> Element | list | str | None: 294 """Return the document content between the start and end tags of the 295 reference. The content returned by this method can spread over several 296 headers and paragraphs. 297 By default, the content is returned as an "office:text" odf element. 298 299 300 Arguments: 301 302 no_header -- boolean (default to False), translate existing headers 303 tags "text:h" into paragraphs "text:p". 304 305 clean -- boolean (default to True), suppress unwanted tags. Striped 306 tags are : 'text:change', 'text:change-start', 307 'text:change-end', 'text:reference-mark', 308 'text:reference-mark-start', 'text:reference-mark-end'. 309 310 as_xml -- boolean (default to False), format the returned content as 311 a XML string (serialization). 312 313 as_list -- boolean (default to False), do not embed the returned 314 content in a "office:text'" element, instead simply 315 return a raw list of odf elements. 316 """ 317 name = self.name 318 parent = self.parent 319 if parent is None: 320 raise ValueError("Reference need some upper document part") 321 body = self.document_body 322 if not body: 323 body = parent 324 end = body.get_reference_mark_end(name=name) 325 if end is None: 326 raise ValueError("No reference-end found") 327 start = self 328 return _get_referenced(body, start, end, no_header, clean, as_xml, as_list) 329 330 def delete(self, child: Element | None = None, keep_tail: bool = True) -> None: 331 """Delete the given element from the XML tree. If no element is given, 332 "self" is deleted. The XML library may allow to continue to use an 333 element now "orphan" as long as you have a reference to it. 334 335 For odf_reference_mark_start : delete the reference-end tag if exists. 336 337 Arguments: 338 339 child -- Element 340 341 keep_tail -- boolean (default to True), True for most usages. 342 """ 343 if child is not None: # act like normal delete 344 return super().delete(child, keep_tail) 345 name = self.name 346 parent = self.parent 347 if parent is None: 348 raise ValueError("Can't delete the root element") 349 body = self.document_body 350 if not body: 351 body = parent 352 end = body.get_reference_mark_end(name=name) 353 if end: 354 end.delete() 355 # act like normal delete 356 return super().delete()
The "text:reference-mark-start" element represents the start of a range reference.
263 def __init__(self, name: str = "", **kwargs: Any) -> None: 264 """The "text:reference-mark-start" element represent the start of a range 265 reference. 266 Consider using the wrapper: odfdo.paragraph.set_reference_mark() 267 268 Arguments: 269 270 name -- str 271 """ 272 super().__init__(**kwargs) 273 if self._do_init: 274 self.name = name
The "text:reference-mark-start" element represent the start of a range reference. Consider using the wrapper: odfdo.paragraph.set_reference_mark()
Arguments:
name -- str
276 def referenced_text(self) -> str: 277 """Return the text between reference-mark-start and reference-mark-end.""" 278 name = self.name 279 request = ( 280 f"//text()" 281 f"[preceding::text:reference-mark-start[@text:name='{name}'] " 282 f"and following::text:reference-mark-end[@text:name='{name}']]" 283 ) 284 result = " ".join(str(x) for x in self.xpath(request)) 285 return result
Return the text between reference-mark-start and reference-mark-end.
287 def get_referenced( 288 self, 289 no_header: bool = False, 290 clean: bool = True, 291 as_xml: bool = False, 292 as_list: bool = False, 293 ) -> Element | list | str | None: 294 """Return the document content between the start and end tags of the 295 reference. The content returned by this method can spread over several 296 headers and paragraphs. 297 By default, the content is returned as an "office:text" odf element. 298 299 300 Arguments: 301 302 no_header -- boolean (default to False), translate existing headers 303 tags "text:h" into paragraphs "text:p". 304 305 clean -- boolean (default to True), suppress unwanted tags. Striped 306 tags are : 'text:change', 'text:change-start', 307 'text:change-end', 'text:reference-mark', 308 'text:reference-mark-start', 'text:reference-mark-end'. 309 310 as_xml -- boolean (default to False), format the returned content as 311 a XML string (serialization). 312 313 as_list -- boolean (default to False), do not embed the returned 314 content in a "office:text'" element, instead simply 315 return a raw list of odf elements. 316 """ 317 name = self.name 318 parent = self.parent 319 if parent is None: 320 raise ValueError("Reference need some upper document part") 321 body = self.document_body 322 if not body: 323 body = parent 324 end = body.get_reference_mark_end(name=name) 325 if end is None: 326 raise ValueError("No reference-end found") 327 start = self 328 return _get_referenced(body, start, end, no_header, clean, as_xml, as_list)
Return the document content between the start and end tags of the reference. The content returned by this method can spread over several headers and paragraphs. By default, the content is returned as an "office:text" odf element.
Arguments:
no_header -- boolean (default to False), translate existing headers
tags "text:h" into paragraphs "text:p".
clean -- boolean (default to True), suppress unwanted tags. Striped
tags are : 'text:change', 'text:change-start',
'text:change-end', 'text:reference-mark',
'text:reference-mark-start', 'text:reference-mark-end'.
as_xml -- boolean (default to False), format the returned content as
a XML string (serialization).
as_list -- boolean (default to False), do not embed the returned
content in a "office:text'" element, instead simply
return a raw list of odf elements.
330 def delete(self, child: Element | None = None, keep_tail: bool = True) -> None: 331 """Delete the given element from the XML tree. If no element is given, 332 "self" is deleted. The XML library may allow to continue to use an 333 element now "orphan" as long as you have a reference to it. 334 335 For odf_reference_mark_start : delete the reference-end tag if exists. 336 337 Arguments: 338 339 child -- Element 340 341 keep_tail -- boolean (default to True), True for most usages. 342 """ 343 if child is not None: # act like normal delete 344 return super().delete(child, keep_tail) 345 name = self.name 346 parent = self.parent 347 if parent is None: 348 raise ValueError("Can't delete the root element") 349 body = self.document_body 350 if not body: 351 body = parent 352 end = body.get_reference_mark_end(name=name) 353 if end: 354 end.delete() 355 # act like normal delete 356 return super().delete()
Delete the given element from the XML tree. If no element is given, "self" is deleted. The XML library may allow to continue to use an element now "orphan" as long as you have a reference to it.
For odf_reference_mark_start : delete the reference-end tag if exists.
Arguments:
child -- Element
keep_tail -- boolean (default to True), True for most usages.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
52class Row(Element): 53 """ODF table row "table:table-row" """ 54 55 _tag = "table:table-row" 56 _caching = True 57 _append = Element.append 58 59 def __init__( 60 self, 61 width: int | None = None, 62 repeated: int | None = None, 63 style: str | None = None, 64 **kwargs: Any, 65 ) -> None: 66 """create a Row, optionally filled with "width" number of cells. 67 68 Rows contain cells, their number determine the number of columns. 69 70 You don't generally have to create rows by hand, use the Table API. 71 72 Arguments: 73 74 width -- int 75 76 repeated -- int 77 78 style -- str 79 """ 80 super().__init__(**kwargs) 81 self.y = None 82 if not hasattr(self, "_indexes"): 83 self._indexes = {} 84 self._indexes["_rmap"] = {} 85 if not hasattr(self, "_rmap"): 86 self._compute_row_cache() 87 if not hasattr(self, "_tmap"): 88 self._tmap = [] 89 self._cmap = [] 90 if self._do_init: 91 if width is not None: 92 for _i in range(width): 93 self.append(Cell()) # type:ignore 94 if repeated: 95 self.repeated = repeated 96 if style is not None: 97 self.style = style 98 self._compute_row_cache() 99 100 def __repr__(self) -> str: 101 return f"<{self.__class__.__name__} y={self.y}>" 102 103 def _get_cells(self) -> list[Element]: 104 return self.get_elements(_xpath_cell) 105 106 def _translate_row_coordinates( 107 self, 108 coord: tuple | list | str, 109 ) -> tuple[int | None, int | None]: 110 xyzt = convert_coordinates(coord) 111 if len(xyzt) == 2: 112 x, z = xyzt 113 else: 114 x, _, z, __ = xyzt 115 if x and x < 0: 116 x = increment(x, self.width) 117 if z and z < 0: 118 z = increment(z, self.width) 119 return (x, z) 120 121 def _compute_row_cache(self) -> None: 122 idx_repeated_seq = self.elements_repeated_sequence( 123 _xpath_cell, "table:number-columns-repeated" 124 ) 125 self._rmap = make_cache_map(idx_repeated_seq) 126 127 # Public API 128 129 @property 130 def clone(self) -> Row: 131 clone = Element.clone.fget(self) # type: ignore 132 clone.y = self.y 133 if hasattr(self, "_tmap"): 134 if hasattr(self, "_rmap"): 135 clone._rmap = self._rmap[:] 136 clone._tmap = self._tmap[:] 137 clone._cmap = self._cmap[:] 138 return clone 139 140 def _set_repeated(self, repeated: int | None) -> None: 141 """Method Internal only. Set the numnber of times the row is 142 repeated, or None to delete it. Without changing cache. 143 144 Arguments: 145 146 repeated -- int 147 """ 148 if repeated is None or repeated < 2: 149 with contextlib.suppress(KeyError): 150 self.del_attribute("table:number-rows-repeated") 151 return 152 self.set_attribute("table:number-rows-repeated", str(repeated)) 153 154 @property 155 def repeated(self) -> int | None: 156 """Get / set the number of times the row is repeated. 157 158 Always None when using the table API. 159 160 Return: int or None 161 """ 162 repeated = self.get_attribute("table:number-rows-repeated") 163 if repeated is None: 164 return None 165 return int(repeated) 166 167 @repeated.setter 168 def repeated(self, repeated: int | None) -> None: 169 self._set_repeated(repeated) 170 # update cache 171 current: Element = self 172 while True: 173 # look for Table, parent may be group of rows 174 upper = current.parent 175 if not upper: 176 # lonely row 177 return 178 # parent may be group of rows, not table 179 if isinstance(upper, Element) and upper._tag == "table:table": 180 break 181 current = upper 182 # fixme : need to optimize this 183 if isinstance(upper, Element) and upper._tag == "table:table": 184 upper._compute_table_cache() 185 if hasattr(self, "_tmap"): 186 del self._tmap[:] 187 self._tmap.extend(upper._tmap) 188 else: 189 self._tmap = upper._tmap 190 191 @property 192 def style(self) -> str | None: 193 """Get /set the style of the row itself. 194 195 Return: str 196 """ 197 return self.get_attribute("table:style-name") # type: ignore 198 199 @style.setter 200 def style(self, style: str | Element) -> None: 201 self.set_style_attribute("table:style-name", style) 202 203 @property 204 def width(self) -> int: 205 """Get the number of expected cells in the row, i.e. addition 206 repetitions. 207 208 Return: int 209 """ 210 try: 211 value = self._rmap[-1] + 1 212 except Exception: 213 value = 0 214 return value 215 216 def _translate_x_from_any(self, x: str | int) -> int: 217 return translate_from_any(x, self.width, 0) 218 219 def traverse( # noqa: C901 220 self, 221 start: int | None = None, 222 end: int | None = None, 223 ) -> Iterator[Cell]: 224 """Yield as many cell elements as expected cells in the row, i.e. 225 expand repetitions by returning the same cell as many times as 226 necessary. 227 228 Arguments: 229 230 start -- int 231 232 end -- int 233 234 Copies are returned, use set_cell() to push them back. 235 """ 236 idx = -1 237 before = -1 238 x = 0 239 cell: Cell 240 if start is None and end is None: 241 for juska in self._rmap: 242 idx += 1 243 if idx in self._indexes["_rmap"]: 244 cell = self._indexes["_rmap"][idx] 245 else: 246 cell = self._get_element_idx2(_xpath_cell_idx, idx) # type: ignore 247 if not isinstance(cell, Cell): 248 raise TypeError(f"Not a cell: {cell!r}") 249 self._indexes["_rmap"][idx] = cell 250 repeated = juska - before 251 before = juska 252 for _i in range(repeated or 1): 253 # Return a copy without the now obsolete repetition 254 if cell is None: 255 cell = Cell() 256 else: 257 cell = cell.clone 258 if repeated > 1: 259 cell.repeated = None 260 cell.y = self.y 261 cell.x = x 262 x += 1 263 yield cell 264 else: 265 if start is None: 266 start = 0 267 start = max(0, start) 268 if end is None: 269 try: 270 end = self._rmap[-1] 271 except Exception: 272 end = -1 273 start_map = find_odf_idx(self._rmap, start) 274 if start_map is None: 275 return 276 if start_map > 0: 277 before = self._rmap[start_map - 1] 278 idx = start_map - 1 279 before = start - 1 280 x = start 281 for juska in self._rmap[start_map:]: 282 idx += 1 283 if idx in self._indexes["_rmap"]: 284 cell = self._indexes["_rmap"][idx] 285 else: 286 cell = self._get_element_idx2(_xpath_cell_idx, idx) # type: ignore 287 if not isinstance(cell, Cell): 288 raise TypeError(f"Not a cell: {cell!r}") 289 self._indexes["_rmap"][idx] = cell 290 repeated = juska - before 291 before = juska 292 for _i in range(repeated or 1): 293 if x <= end: 294 if cell is None: 295 cell = Cell() 296 else: 297 cell = cell.clone 298 if repeated > 1 or (x == start and start > 0): 299 cell.repeated = None 300 cell.y = self.y 301 cell.x = x 302 x += 1 303 yield cell 304 305 def get_cells( 306 self, 307 coord: str | tuple | None = None, 308 style: str | None = None, 309 content: str | None = None, 310 cell_type: str | None = None, 311 ) -> list[Cell]: 312 """Get the list of cells matching the criteria. 313 314 Filter by cell_type, with cell_type 'all' will retrieve cells of any 315 type, aka non empty cells. 316 317 Filter by coordinates will retrieve the amount of cells defined by 318 'coord', minus the other filters. 319 320 Arguments: 321 322 coord -- str or tuple of int : coordinates 323 324 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 325 'currency', 'percentage' or 'all' 326 327 content -- str regex 328 329 style -- str 330 331 Return: list of Cell 332 """ 333 # fixme : not clones ? 334 if coord: 335 x, z = self._translate_row_coordinates(coord) 336 else: 337 x = None 338 z = None 339 if cell_type: 340 cell_type = cell_type.lower().strip() 341 cells: list[Cell] = [] 342 for cell in self.traverse(start=x, end=z): 343 # Filter the cells by cell_type 344 if cell_type: 345 ctype = cell.type 346 if not ctype or not (ctype == cell_type or cell_type == "all"): 347 continue 348 # Filter the cells with the regex 349 if content and not cell.match(content): 350 continue 351 # Filter the cells with the style 352 if style and style != cell.style: 353 continue 354 cells.append(cell) 355 return cells 356 357 def _get_cell2(self, x: int, clone: bool = True) -> Cell | None: 358 if x >= self.width: 359 return Cell() 360 if clone: 361 return self._get_cell2_base(x).clone # type: ignore 362 else: 363 return self._get_cell2_base(x) 364 365 def _get_cell2_base(self, x: int) -> Cell | None: 366 idx = find_odf_idx(self._rmap, x) 367 cell: Cell 368 if idx is not None: 369 if idx in self._indexes["_rmap"]: 370 cell = self._indexes["_rmap"][idx] 371 else: 372 cell = self._get_element_idx2(_xpath_cell_idx, idx) # type: ignore 373 self._indexes["_rmap"][idx] = cell 374 return cell 375 return None 376 377 def get_cell(self, x: int, clone: bool = True) -> Cell | None: 378 """Get the cell at position "x" starting from 0. Alphabetical 379 positions like "D" are accepted. 380 381 A copy is returned, use set_cell() to push it back. 382 383 Arguments: 384 385 x -- int or str 386 387 Return: Cell | None 388 """ 389 x = self._translate_x_from_any(x) 390 cell = self._get_cell2(x, clone=clone) 391 if not cell: 392 return None 393 cell.y = self.y 394 cell.x = x 395 return cell 396 397 def get_value( 398 self, 399 x: int | str, 400 get_type: bool = False, 401 ) -> Any | tuple[Any, str]: 402 """Shortcut to get the value of the cell at position "x". 403 If get_type is True, returns the tuples (value, ODF type). 404 405 If the cell is empty, returns None or (None, None) 406 407 See get_cell() and Cell.get_value(). 408 """ 409 if get_type: 410 x = self._translate_x_from_any(x) 411 cell = self._get_cell2_base(x) 412 if cell is None: 413 return (None, None) 414 return cell.get_value(get_type=get_type) 415 x = self._translate_x_from_any(x) 416 cell = self._get_cell2_base(x) 417 if cell is None: 418 return None 419 return cell.get_value() 420 421 def set_cell( 422 self, 423 x: int | str, 424 cell: Cell | None = None, 425 clone: bool = True, 426 ) -> Cell: 427 """Push the cell back in the row at position "x" starting from 0. 428 Alphabetical positions like "D" are accepted. 429 430 Arguments: 431 432 x -- int or str 433 434 returns the cell with x and y updated 435 """ 436 cell_back: Cell 437 if cell is None: 438 cell = Cell() 439 repeated = 1 440 clone = False 441 else: 442 repeated = cell.repeated or 1 443 x = self._translate_x_from_any(x) 444 # Outside the defined row 445 diff = x - self.width 446 if diff == 0: 447 cell_back = self.append_cell(cell, _repeated=repeated, clone=clone) 448 elif diff > 0: 449 self.append_cell(Cell(repeated=diff), _repeated=diff, clone=False) 450 cell_back = self.append_cell(cell, _repeated=repeated, clone=clone) 451 else: 452 # Inside the defined row 453 set_item_in_vault(x, cell, self, _xpath_cell_idx, "_rmap", clone=clone) 454 cell.x = x 455 cell.y = self.y 456 cell_back = cell 457 return cell_back 458 459 def set_value( 460 self, 461 x: int | str, 462 value: Any, 463 style: str | None = None, 464 cell_type: str | None = None, 465 currency: str | None = None, 466 ) -> None: 467 """Shortcut to set the value of the cell at position "x". 468 469 Arguments: 470 471 x -- int or str 472 473 value -- Python type 474 475 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 476 'string' or 'time' 477 478 currency -- three-letter str 479 480 style -- str 481 482 See get_cell() and Cell.get_value(). 483 """ 484 self.set_cell( 485 x, 486 Cell(value, style=style, cell_type=cell_type, currency=currency), 487 clone=False, 488 ) 489 490 def insert_cell( 491 self, 492 x: int | str, 493 cell: Cell | None = None, 494 clone: bool = True, 495 ) -> Cell: 496 """Insert the given cell at position "x" starting from 0. If no cell 497 is given, an empty one is created. 498 499 Alphabetical positions like "D" are accepted. 500 501 Do not use when working on a table, use Table.insert_cell(). 502 503 Arguments: 504 505 x -- int or str 506 507 cell -- Cell 508 509 returns the cell with x and y updated 510 """ 511 cell_back: Cell 512 if cell is None: 513 cell = Cell() 514 x = self._translate_x_from_any(x) 515 # Outside the defined row 516 diff = x - self.width 517 if diff < 0: 518 insert_item_in_vault(x, cell, self, _xpath_cell_idx, "_rmap") 519 cell.x = x 520 cell.y = self.y 521 cell_back = cell 522 elif diff == 0: 523 cell_back = self.append_cell(cell, clone=clone) 524 else: 525 self.append_cell(Cell(repeated=diff), _repeated=diff, clone=False) 526 cell_back = self.append_cell(cell, clone=clone) 527 return cell_back 528 529 def extend_cells(self, cells: Iterable[Cell] | None = None) -> None: 530 if cells is None: 531 cells = [] 532 self.extend(cells) 533 self._compute_row_cache() 534 535 def append_cell( 536 self, 537 cell: Cell | None = None, 538 clone: bool = True, 539 _repeated: int | None = None, 540 ) -> Cell: 541 """Append the given cell at the end of the row. Repeated cells are 542 accepted. If no cell is given, an empty one is created. 543 544 Do not use when working on a table, use Table.append_cell(). 545 546 Arguments: 547 548 cell -- Cell 549 550 _repeated -- (optional), repeated value of the row 551 552 returns the cell with x and y updated 553 """ 554 if cell is None: 555 cell = Cell() 556 clone = False 557 if clone: 558 cell = cell.clone 559 self._append(cell) 560 if _repeated is None: 561 _repeated = cell.repeated or 1 562 self._rmap = insert_map_once(self._rmap, len(self._rmap), _repeated) 563 cell.x = self.width - 1 564 cell.y = self.y 565 return cell 566 567 # fix for unit test and typos 568 append = append_cell # type: ignore 569 570 def delete_cell(self, x: int | str) -> None: 571 """Delete the cell at the given position "x" starting from 0. 572 Alphabetical positions like "D" are accepted. 573 574 Cells on the right will be shifted to the left. In a table, other 575 rows remain unaffected. 576 577 Arguments: 578 579 x -- int or str 580 """ 581 x = self._translate_x_from_any(x) 582 if x >= self.width: 583 return 584 delete_item_in_vault(x, self, _xpath_cell_idx, "_rmap") 585 586 def get_values( 587 self, 588 coord: str | tuple | None = None, 589 cell_type: str | None = None, 590 complete: bool = False, 591 get_type: bool = False, 592 ) -> list[Any | tuple[Any, Any]]: 593 """Shortcut to get the cell values in this row. 594 595 Filter by cell_type, with cell_type 'all' will retrieve cells of any 596 type, aka non empty cells. 597 If cell_type is used and complete is True, missing values are 598 replaced by None. 599 If cell_type is None, complete is always True : with no cell type 600 queried, get_values() returns None for each empty cell, the length 601 of the list is equal to the length of the row (depending on 602 coordinates use). 603 604 If get_type is True, returns a tuple (value, ODF type of value), or 605 (None, None) for empty cells if complete is True. 606 607 Filter by coordinates will retrieve the amount of cells defined by 608 coordinates with None for empty cells, except when using cell_type. 609 610 611 Arguments: 612 613 coord -- str or tuple of int : coordinates in row 614 615 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 616 'currency', 'percentage' or 'all' 617 618 complete -- boolean 619 620 get_type -- boolean 621 622 Return: list of Python types, or list of tuples. 623 """ 624 if coord: 625 x, z = self._translate_row_coordinates(coord) 626 else: 627 x = None 628 z = None 629 if cell_type: 630 cell_type = cell_type.lower().strip() 631 values: list[Any | tuple[Any, Any]] = [] 632 for cell in self.traverse(start=x, end=z): 633 # Filter the cells by cell_type 634 ctype = cell.type 635 if not ctype or not (ctype == cell_type or cell_type == "all"): 636 if complete: 637 if get_type: 638 values.append((None, None)) 639 else: 640 values.append(None) 641 continue 642 values.append(cell.get_value(get_type=get_type)) 643 return values 644 else: 645 return [ 646 cell.get_value(get_type=get_type) 647 for cell in self.traverse(start=x, end=z) 648 ] 649 650 def set_cells( 651 self, 652 cells: list[Cell] | tuple[Cell] | None = None, 653 start: int | str = 0, 654 clone: bool = True, 655 ) -> None: 656 """Set the cells in the row, from the 'start' column. 657 This method does not clear the row, use row.clear() before to start 658 with an empty row. 659 660 Arguments: 661 662 cells -- list of cells 663 664 start -- int or str 665 """ 666 if cells is None: 667 cells = [] 668 if start is None: 669 start = 0 670 else: 671 start = self._translate_x_from_any(start) 672 if start == 0 and clone is False and (len(cells) >= self.width): 673 self.clear() 674 self.extend_cells(cells) 675 else: 676 x = start 677 for cell in cells: 678 self.set_cell(x, cell, clone=clone) 679 if cell: 680 x += cell.repeated or 1 681 else: 682 x += 1 683 684 def set_values( 685 self, 686 values: list[Any], 687 start: int | str = 0, 688 style: str | None = None, 689 cell_type: str | None = None, 690 currency: str | None = None, 691 ) -> None: 692 """Shortcut to set the value of cells in the row, from the 'start' 693 column vith values. 694 This method does not clear the row, use row.clear() before to start 695 with an empty row. 696 697 Arguments: 698 699 values -- list of Python types 700 701 start -- int or str 702 703 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 704 'currency' or 'percentage' 705 706 currency -- three-letter str 707 708 style -- cell style 709 """ 710 # fixme : if values n, n+ are same, use repeat 711 if start is None: 712 start = 0 713 else: 714 start = self._translate_x_from_any(start) 715 if start == 0 and (len(values) >= self.width): 716 self.clear() 717 cells = [ 718 Cell(value, style=style, cell_type=cell_type, currency=currency) 719 for value in values 720 ] 721 self.extend_cells(cells) 722 else: 723 x = start 724 for value in values: 725 self.set_cell( 726 x, 727 Cell(value, style=style, cell_type=cell_type, currency=currency), 728 clone=False, 729 ) 730 x += 1 731 732 def rstrip(self, aggressive: bool = False) -> None: 733 """Remove *in-place* empty cells at the right of the row. An empty 734 cell has no value but can have style. If "aggressive" is True, style 735 is ignored. 736 737 Arguments: 738 739 aggressive -- bool 740 """ 741 for cell in reversed(self._get_cells()): 742 if not cell.is_empty(aggressive=aggressive): # type: ignore 743 break 744 self.delete(cell) 745 self._compute_row_cache() 746 self._indexes["_rmap"] = {} 747 748 def _current_length(self) -> int: 749 """Return the current estimated length of the row. 750 751 Return: int 752 """ 753 idx_repeated_seq = self.elements_repeated_sequence( 754 _xpath_cell, "table:number-columns-repeated" 755 ) 756 repeated = [item[1] for item in idx_repeated_seq] 757 if repeated: 758 return sum(repeated) 759 return 1 760 761 def minimized_width(self) -> int: 762 """Return the length of the row if the last repeated sequence is 763 reduced to one. 764 765 Return: int 766 """ 767 idx_repeated_seq = self.elements_repeated_sequence( 768 _xpath_cell, "table:number-columns-repeated" 769 ) 770 repeated = [item[1] for item in idx_repeated_seq] 771 if repeated: 772 cell = self.last_cell() 773 if cell is not None and cell.is_empty(aggressive=True): 774 repeated[-1] = 1 775 min_width = sum(repeated) 776 else: 777 min_width = 1 778 self._compute_row_cache() 779 self._indexes["_rmap"] = {} 780 return min_width 781 782 def last_cell(self) -> Cell | None: 783 """Return the las cell of the row. 784 785 Return Cell | None 786 """ 787 try: 788 return self._get_cells()[-1] # type: ignore 789 except IndexError: 790 return None 791 792 def force_width(self, width: int) -> None: 793 """Change the repeated property of the last cell of the row 794 to comply with the required max width. 795 796 Arguments: 797 798 width -- int 799 """ 800 cell = self.last_cell() 801 if cell is None or not cell.is_empty(aggressive=True): 802 return 803 repeated = cell.repeated 804 if repeated is None: 805 return 806 # empty repeated cell 807 delta = self._current_length() - width 808 if delta > 0: 809 cell._set_repeated(repeated - delta) 810 self._compute_row_cache() 811 812 def is_empty(self, aggressive: bool = False) -> bool: 813 """Return whether every cell in the row has no value or the value 814 evaluates to False (empty string), and no style. 815 816 If aggressive is True, empty cells with style are considered empty. 817 818 Arguments: 819 820 aggressive -- bool 821 822 Return: bool 823 """ 824 return all(cell.is_empty(aggressive=aggressive) for cell in self._get_cells()) # type: ignore
ODF table row "table:table-row"
59 def __init__( 60 self, 61 width: int | None = None, 62 repeated: int | None = None, 63 style: str | None = None, 64 **kwargs: Any, 65 ) -> None: 66 """create a Row, optionally filled with "width" number of cells. 67 68 Rows contain cells, their number determine the number of columns. 69 70 You don't generally have to create rows by hand, use the Table API. 71 72 Arguments: 73 74 width -- int 75 76 repeated -- int 77 78 style -- str 79 """ 80 super().__init__(**kwargs) 81 self.y = None 82 if not hasattr(self, "_indexes"): 83 self._indexes = {} 84 self._indexes["_rmap"] = {} 85 if not hasattr(self, "_rmap"): 86 self._compute_row_cache() 87 if not hasattr(self, "_tmap"): 88 self._tmap = [] 89 self._cmap = [] 90 if self._do_init: 91 if width is not None: 92 for _i in range(width): 93 self.append(Cell()) # type:ignore 94 if repeated: 95 self.repeated = repeated 96 if style is not None: 97 self.style = style 98 self._compute_row_cache()
create a Row, optionally filled with "width" number of cells.
Rows contain cells, their number determine the number of columns.
You don't generally have to create rows by hand, use the Table API.
Arguments:
width -- int
repeated -- int
style -- str
154 @property 155 def repeated(self) -> int | None: 156 """Get / set the number of times the row is repeated. 157 158 Always None when using the table API. 159 160 Return: int or None 161 """ 162 repeated = self.get_attribute("table:number-rows-repeated") 163 if repeated is None: 164 return None 165 return int(repeated)
Get / set the number of times the row is repeated.
Always None when using the table API.
Return: int or None
191 @property 192 def style(self) -> str | None: 193 """Get /set the style of the row itself. 194 195 Return: str 196 """ 197 return self.get_attribute("table:style-name") # type: ignore
Get /set the style of the row itself.
Return: str
203 @property 204 def width(self) -> int: 205 """Get the number of expected cells in the row, i.e. addition 206 repetitions. 207 208 Return: int 209 """ 210 try: 211 value = self._rmap[-1] + 1 212 except Exception: 213 value = 0 214 return value
Get the number of expected cells in the row, i.e. addition repetitions.
Return: int
219 def traverse( # noqa: C901 220 self, 221 start: int | None = None, 222 end: int | None = None, 223 ) -> Iterator[Cell]: 224 """Yield as many cell elements as expected cells in the row, i.e. 225 expand repetitions by returning the same cell as many times as 226 necessary. 227 228 Arguments: 229 230 start -- int 231 232 end -- int 233 234 Copies are returned, use set_cell() to push them back. 235 """ 236 idx = -1 237 before = -1 238 x = 0 239 cell: Cell 240 if start is None and end is None: 241 for juska in self._rmap: 242 idx += 1 243 if idx in self._indexes["_rmap"]: 244 cell = self._indexes["_rmap"][idx] 245 else: 246 cell = self._get_element_idx2(_xpath_cell_idx, idx) # type: ignore 247 if not isinstance(cell, Cell): 248 raise TypeError(f"Not a cell: {cell!r}") 249 self._indexes["_rmap"][idx] = cell 250 repeated = juska - before 251 before = juska 252 for _i in range(repeated or 1): 253 # Return a copy without the now obsolete repetition 254 if cell is None: 255 cell = Cell() 256 else: 257 cell = cell.clone 258 if repeated > 1: 259 cell.repeated = None 260 cell.y = self.y 261 cell.x = x 262 x += 1 263 yield cell 264 else: 265 if start is None: 266 start = 0 267 start = max(0, start) 268 if end is None: 269 try: 270 end = self._rmap[-1] 271 except Exception: 272 end = -1 273 start_map = find_odf_idx(self._rmap, start) 274 if start_map is None: 275 return 276 if start_map > 0: 277 before = self._rmap[start_map - 1] 278 idx = start_map - 1 279 before = start - 1 280 x = start 281 for juska in self._rmap[start_map:]: 282 idx += 1 283 if idx in self._indexes["_rmap"]: 284 cell = self._indexes["_rmap"][idx] 285 else: 286 cell = self._get_element_idx2(_xpath_cell_idx, idx) # type: ignore 287 if not isinstance(cell, Cell): 288 raise TypeError(f"Not a cell: {cell!r}") 289 self._indexes["_rmap"][idx] = cell 290 repeated = juska - before 291 before = juska 292 for _i in range(repeated or 1): 293 if x <= end: 294 if cell is None: 295 cell = Cell() 296 else: 297 cell = cell.clone 298 if repeated > 1 or (x == start and start > 0): 299 cell.repeated = None 300 cell.y = self.y 301 cell.x = x 302 x += 1 303 yield cell
Yield as many cell elements as expected cells in the row, i.e. expand repetitions by returning the same cell as many times as necessary.
Arguments:
start -- int
end -- int
Copies are returned, use set_cell() to push them back.
305 def get_cells( 306 self, 307 coord: str | tuple | None = None, 308 style: str | None = None, 309 content: str | None = None, 310 cell_type: str | None = None, 311 ) -> list[Cell]: 312 """Get the list of cells matching the criteria. 313 314 Filter by cell_type, with cell_type 'all' will retrieve cells of any 315 type, aka non empty cells. 316 317 Filter by coordinates will retrieve the amount of cells defined by 318 'coord', minus the other filters. 319 320 Arguments: 321 322 coord -- str or tuple of int : coordinates 323 324 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 325 'currency', 'percentage' or 'all' 326 327 content -- str regex 328 329 style -- str 330 331 Return: list of Cell 332 """ 333 # fixme : not clones ? 334 if coord: 335 x, z = self._translate_row_coordinates(coord) 336 else: 337 x = None 338 z = None 339 if cell_type: 340 cell_type = cell_type.lower().strip() 341 cells: list[Cell] = [] 342 for cell in self.traverse(start=x, end=z): 343 # Filter the cells by cell_type 344 if cell_type: 345 ctype = cell.type 346 if not ctype or not (ctype == cell_type or cell_type == "all"): 347 continue 348 # Filter the cells with the regex 349 if content and not cell.match(content): 350 continue 351 # Filter the cells with the style 352 if style and style != cell.style: 353 continue 354 cells.append(cell) 355 return cells
Get the list of cells matching the criteria.
Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells.
Filter by coordinates will retrieve the amount of cells defined by 'coord', minus the other filters.
Arguments:
coord -- str or tuple of int : coordinates
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency', 'percentage' or 'all'
content -- str regex
style -- str
Return: list of Cell
377 def get_cell(self, x: int, clone: bool = True) -> Cell | None: 378 """Get the cell at position "x" starting from 0. Alphabetical 379 positions like "D" are accepted. 380 381 A copy is returned, use set_cell() to push it back. 382 383 Arguments: 384 385 x -- int or str 386 387 Return: Cell | None 388 """ 389 x = self._translate_x_from_any(x) 390 cell = self._get_cell2(x, clone=clone) 391 if not cell: 392 return None 393 cell.y = self.y 394 cell.x = x 395 return cell
Get the cell at position "x" starting from 0. Alphabetical positions like "D" are accepted.
A copy is returned, use set_cell() to push it back.
Arguments:
x -- int or str
Return: Cell | None
397 def get_value( 398 self, 399 x: int | str, 400 get_type: bool = False, 401 ) -> Any | tuple[Any, str]: 402 """Shortcut to get the value of the cell at position "x". 403 If get_type is True, returns the tuples (value, ODF type). 404 405 If the cell is empty, returns None or (None, None) 406 407 See get_cell() and Cell.get_value(). 408 """ 409 if get_type: 410 x = self._translate_x_from_any(x) 411 cell = self._get_cell2_base(x) 412 if cell is None: 413 return (None, None) 414 return cell.get_value(get_type=get_type) 415 x = self._translate_x_from_any(x) 416 cell = self._get_cell2_base(x) 417 if cell is None: 418 return None 419 return cell.get_value()
Shortcut to get the value of the cell at position "x". If get_type is True, returns the tuples (value, ODF type).
If the cell is empty, returns None or (None, None)
See get_cell() and Cell.get_value().
421 def set_cell( 422 self, 423 x: int | str, 424 cell: Cell | None = None, 425 clone: bool = True, 426 ) -> Cell: 427 """Push the cell back in the row at position "x" starting from 0. 428 Alphabetical positions like "D" are accepted. 429 430 Arguments: 431 432 x -- int or str 433 434 returns the cell with x and y updated 435 """ 436 cell_back: Cell 437 if cell is None: 438 cell = Cell() 439 repeated = 1 440 clone = False 441 else: 442 repeated = cell.repeated or 1 443 x = self._translate_x_from_any(x) 444 # Outside the defined row 445 diff = x - self.width 446 if diff == 0: 447 cell_back = self.append_cell(cell, _repeated=repeated, clone=clone) 448 elif diff > 0: 449 self.append_cell(Cell(repeated=diff), _repeated=diff, clone=False) 450 cell_back = self.append_cell(cell, _repeated=repeated, clone=clone) 451 else: 452 # Inside the defined row 453 set_item_in_vault(x, cell, self, _xpath_cell_idx, "_rmap", clone=clone) 454 cell.x = x 455 cell.y = self.y 456 cell_back = cell 457 return cell_back
Push the cell back in the row at position "x" starting from 0. Alphabetical positions like "D" are accepted.
Arguments:
x -- int or str
returns the cell with x and y updated
459 def set_value( 460 self, 461 x: int | str, 462 value: Any, 463 style: str | None = None, 464 cell_type: str | None = None, 465 currency: str | None = None, 466 ) -> None: 467 """Shortcut to set the value of the cell at position "x". 468 469 Arguments: 470 471 x -- int or str 472 473 value -- Python type 474 475 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 476 'string' or 'time' 477 478 currency -- three-letter str 479 480 style -- str 481 482 See get_cell() and Cell.get_value(). 483 """ 484 self.set_cell( 485 x, 486 Cell(value, style=style, cell_type=cell_type, currency=currency), 487 clone=False, 488 )
Shortcut to set the value of the cell at position "x".
Arguments:
x -- int or str
value -- Python type
cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
'string' or 'time'
currency -- three-letter str
style -- str
See get_cell() and Cell.get_value().
490 def insert_cell( 491 self, 492 x: int | str, 493 cell: Cell | None = None, 494 clone: bool = True, 495 ) -> Cell: 496 """Insert the given cell at position "x" starting from 0. If no cell 497 is given, an empty one is created. 498 499 Alphabetical positions like "D" are accepted. 500 501 Do not use when working on a table, use Table.insert_cell(). 502 503 Arguments: 504 505 x -- int or str 506 507 cell -- Cell 508 509 returns the cell with x and y updated 510 """ 511 cell_back: Cell 512 if cell is None: 513 cell = Cell() 514 x = self._translate_x_from_any(x) 515 # Outside the defined row 516 diff = x - self.width 517 if diff < 0: 518 insert_item_in_vault(x, cell, self, _xpath_cell_idx, "_rmap") 519 cell.x = x 520 cell.y = self.y 521 cell_back = cell 522 elif diff == 0: 523 cell_back = self.append_cell(cell, clone=clone) 524 else: 525 self.append_cell(Cell(repeated=diff), _repeated=diff, clone=False) 526 cell_back = self.append_cell(cell, clone=clone) 527 return cell_back
Insert the given cell at position "x" starting from 0. If no cell is given, an empty one is created.
Alphabetical positions like "D" are accepted.
Do not use when working on a table, use Table.insert_cell().
Arguments:
x -- int or str
cell -- Cell
returns the cell with x and y updated
535 def append_cell( 536 self, 537 cell: Cell | None = None, 538 clone: bool = True, 539 _repeated: int | None = None, 540 ) -> Cell: 541 """Append the given cell at the end of the row. Repeated cells are 542 accepted. If no cell is given, an empty one is created. 543 544 Do not use when working on a table, use Table.append_cell(). 545 546 Arguments: 547 548 cell -- Cell 549 550 _repeated -- (optional), repeated value of the row 551 552 returns the cell with x and y updated 553 """ 554 if cell is None: 555 cell = Cell() 556 clone = False 557 if clone: 558 cell = cell.clone 559 self._append(cell) 560 if _repeated is None: 561 _repeated = cell.repeated or 1 562 self._rmap = insert_map_once(self._rmap, len(self._rmap), _repeated) 563 cell.x = self.width - 1 564 cell.y = self.y 565 return cell
Append the given cell at the end of the row. Repeated cells are accepted. If no cell is given, an empty one is created.
Do not use when working on a table, use Table.append_cell().
Arguments:
cell -- Cell
_repeated -- (optional), repeated value of the row
returns the cell with x and y updated
535 def append_cell( 536 self, 537 cell: Cell | None = None, 538 clone: bool = True, 539 _repeated: int | None = None, 540 ) -> Cell: 541 """Append the given cell at the end of the row. Repeated cells are 542 accepted. If no cell is given, an empty one is created. 543 544 Do not use when working on a table, use Table.append_cell(). 545 546 Arguments: 547 548 cell -- Cell 549 550 _repeated -- (optional), repeated value of the row 551 552 returns the cell with x and y updated 553 """ 554 if cell is None: 555 cell = Cell() 556 clone = False 557 if clone: 558 cell = cell.clone 559 self._append(cell) 560 if _repeated is None: 561 _repeated = cell.repeated or 1 562 self._rmap = insert_map_once(self._rmap, len(self._rmap), _repeated) 563 cell.x = self.width - 1 564 cell.y = self.y 565 return cell
Append the given cell at the end of the row. Repeated cells are accepted. If no cell is given, an empty one is created.
Do not use when working on a table, use Table.append_cell().
Arguments:
cell -- Cell
_repeated -- (optional), repeated value of the row
returns the cell with x and y updated
570 def delete_cell(self, x: int | str) -> None: 571 """Delete the cell at the given position "x" starting from 0. 572 Alphabetical positions like "D" are accepted. 573 574 Cells on the right will be shifted to the left. In a table, other 575 rows remain unaffected. 576 577 Arguments: 578 579 x -- int or str 580 """ 581 x = self._translate_x_from_any(x) 582 if x >= self.width: 583 return 584 delete_item_in_vault(x, self, _xpath_cell_idx, "_rmap")
Delete the cell at the given position "x" starting from 0. Alphabetical positions like "D" are accepted.
Cells on the right will be shifted to the left. In a table, other rows remain unaffected.
Arguments:
x -- int or str
586 def get_values( 587 self, 588 coord: str | tuple | None = None, 589 cell_type: str | None = None, 590 complete: bool = False, 591 get_type: bool = False, 592 ) -> list[Any | tuple[Any, Any]]: 593 """Shortcut to get the cell values in this row. 594 595 Filter by cell_type, with cell_type 'all' will retrieve cells of any 596 type, aka non empty cells. 597 If cell_type is used and complete is True, missing values are 598 replaced by None. 599 If cell_type is None, complete is always True : with no cell type 600 queried, get_values() returns None for each empty cell, the length 601 of the list is equal to the length of the row (depending on 602 coordinates use). 603 604 If get_type is True, returns a tuple (value, ODF type of value), or 605 (None, None) for empty cells if complete is True. 606 607 Filter by coordinates will retrieve the amount of cells defined by 608 coordinates with None for empty cells, except when using cell_type. 609 610 611 Arguments: 612 613 coord -- str or tuple of int : coordinates in row 614 615 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 616 'currency', 'percentage' or 'all' 617 618 complete -- boolean 619 620 get_type -- boolean 621 622 Return: list of Python types, or list of tuples. 623 """ 624 if coord: 625 x, z = self._translate_row_coordinates(coord) 626 else: 627 x = None 628 z = None 629 if cell_type: 630 cell_type = cell_type.lower().strip() 631 values: list[Any | tuple[Any, Any]] = [] 632 for cell in self.traverse(start=x, end=z): 633 # Filter the cells by cell_type 634 ctype = cell.type 635 if not ctype or not (ctype == cell_type or cell_type == "all"): 636 if complete: 637 if get_type: 638 values.append((None, None)) 639 else: 640 values.append(None) 641 continue 642 values.append(cell.get_value(get_type=get_type)) 643 return values 644 else: 645 return [ 646 cell.get_value(get_type=get_type) 647 for cell in self.traverse(start=x, end=z) 648 ]
Shortcut to get the cell values in this row.
Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells. If cell_type is used and complete is True, missing values are replaced by None. If cell_type is None, complete is always True : with no cell type queried, get_values() returns None for each empty cell, the length of the list is equal to the length of the row (depending on coordinates use).
If get_type is True, returns a tuple (value, ODF type of value), or (None, None) for empty cells if complete is True.
Filter by coordinates will retrieve the amount of cells defined by coordinates with None for empty cells, except when using cell_type.
Arguments:
coord -- str or tuple of int : coordinates in row
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency', 'percentage' or 'all'
complete -- boolean
get_type -- boolean
Return: list of Python types, or list of tuples.
650 def set_cells( 651 self, 652 cells: list[Cell] | tuple[Cell] | None = None, 653 start: int | str = 0, 654 clone: bool = True, 655 ) -> None: 656 """Set the cells in the row, from the 'start' column. 657 This method does not clear the row, use row.clear() before to start 658 with an empty row. 659 660 Arguments: 661 662 cells -- list of cells 663 664 start -- int or str 665 """ 666 if cells is None: 667 cells = [] 668 if start is None: 669 start = 0 670 else: 671 start = self._translate_x_from_any(start) 672 if start == 0 and clone is False and (len(cells) >= self.width): 673 self.clear() 674 self.extend_cells(cells) 675 else: 676 x = start 677 for cell in cells: 678 self.set_cell(x, cell, clone=clone) 679 if cell: 680 x += cell.repeated or 1 681 else: 682 x += 1
Set the cells in the row, from the 'start' column. This method does not clear the row, use row.clear() before to start with an empty row.
Arguments:
cells -- list of cells
start -- int or str
684 def set_values( 685 self, 686 values: list[Any], 687 start: int | str = 0, 688 style: str | None = None, 689 cell_type: str | None = None, 690 currency: str | None = None, 691 ) -> None: 692 """Shortcut to set the value of cells in the row, from the 'start' 693 column vith values. 694 This method does not clear the row, use row.clear() before to start 695 with an empty row. 696 697 Arguments: 698 699 values -- list of Python types 700 701 start -- int or str 702 703 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 704 'currency' or 'percentage' 705 706 currency -- three-letter str 707 708 style -- cell style 709 """ 710 # fixme : if values n, n+ are same, use repeat 711 if start is None: 712 start = 0 713 else: 714 start = self._translate_x_from_any(start) 715 if start == 0 and (len(values) >= self.width): 716 self.clear() 717 cells = [ 718 Cell(value, style=style, cell_type=cell_type, currency=currency) 719 for value in values 720 ] 721 self.extend_cells(cells) 722 else: 723 x = start 724 for value in values: 725 self.set_cell( 726 x, 727 Cell(value, style=style, cell_type=cell_type, currency=currency), 728 clone=False, 729 ) 730 x += 1
Shortcut to set the value of cells in the row, from the 'start' column vith values. This method does not clear the row, use row.clear() before to start with an empty row.
Arguments:
values -- list of Python types
start -- int or str
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency' or 'percentage'
currency -- three-letter str
style -- cell style
732 def rstrip(self, aggressive: bool = False) -> None: 733 """Remove *in-place* empty cells at the right of the row. An empty 734 cell has no value but can have style. If "aggressive" is True, style 735 is ignored. 736 737 Arguments: 738 739 aggressive -- bool 740 """ 741 for cell in reversed(self._get_cells()): 742 if not cell.is_empty(aggressive=aggressive): # type: ignore 743 break 744 self.delete(cell) 745 self._compute_row_cache() 746 self._indexes["_rmap"] = {}
Remove in-place empty cells at the right of the row. An empty cell has no value but can have style. If "aggressive" is True, style is ignored.
Arguments:
aggressive -- bool
761 def minimized_width(self) -> int: 762 """Return the length of the row if the last repeated sequence is 763 reduced to one. 764 765 Return: int 766 """ 767 idx_repeated_seq = self.elements_repeated_sequence( 768 _xpath_cell, "table:number-columns-repeated" 769 ) 770 repeated = [item[1] for item in idx_repeated_seq] 771 if repeated: 772 cell = self.last_cell() 773 if cell is not None and cell.is_empty(aggressive=True): 774 repeated[-1] = 1 775 min_width = sum(repeated) 776 else: 777 min_width = 1 778 self._compute_row_cache() 779 self._indexes["_rmap"] = {} 780 return min_width
Return the length of the row if the last repeated sequence is reduced to one.
Return: int
782 def last_cell(self) -> Cell | None: 783 """Return the las cell of the row. 784 785 Return Cell | None 786 """ 787 try: 788 return self._get_cells()[-1] # type: ignore 789 except IndexError: 790 return None
Return the las cell of the row.
Return Cell | None
792 def force_width(self, width: int) -> None: 793 """Change the repeated property of the last cell of the row 794 to comply with the required max width. 795 796 Arguments: 797 798 width -- int 799 """ 800 cell = self.last_cell() 801 if cell is None or not cell.is_empty(aggressive=True): 802 return 803 repeated = cell.repeated 804 if repeated is None: 805 return 806 # empty repeated cell 807 delta = self._current_length() - width 808 if delta > 0: 809 cell._set_repeated(repeated - delta) 810 self._compute_row_cache()
Change the repeated property of the last cell of the row to comply with the required max width.
Arguments:
width -- int
812 def is_empty(self, aggressive: bool = False) -> bool: 813 """Return whether every cell in the row has no value or the value 814 evaluates to False (empty string), and no style. 815 816 If aggressive is True, empty cells with style are considered empty. 817 818 Arguments: 819 820 aggressive -- bool 821 822 Return: bool 823 """ 824 return all(cell.is_empty(aggressive=aggressive) for cell in self._get_cells()) # type: ignore
Return whether every cell in the row has no value or the value evaluates to False (empty string), and no style.
If aggressive is True, empty cells with style are considered empty.
Arguments:
aggressive -- bool
Return: bool
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- get_between
- insert
- extend
- delete
- replace_element
- strip_elements
- xpath
- clear
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
133class RowGroup(Element): 134 """ "table:table-row-group" group rows with common properties.""" 135 136 # TODO 137 _tag = "table:table-row-group" 138 _caching = True 139 140 def __init__( 141 self, 142 height: int | None = None, 143 width: int | None = None, 144 **kwargs: Any, 145 ) -> None: 146 """Create a group of rows, optionnaly filled with "height" number of 147 rows, of "width" cells each. 148 149 Row group bear style information applied to a series of rows. 150 151 Arguments: 152 153 height -- int 154 155 width -- int 156 """ 157 super().__init__(**kwargs) 158 if self._do_init and height is not None: 159 for _i in range(height): 160 row = Row(width=width) 161 self.append(row)
"table:table-row-group" group rows with common properties.
140 def __init__( 141 self, 142 height: int | None = None, 143 width: int | None = None, 144 **kwargs: Any, 145 ) -> None: 146 """Create a group of rows, optionnaly filled with "height" number of 147 rows, of "width" cells each. 148 149 Row group bear style information applied to a series of rows. 150 151 Arguments: 152 153 height -- int 154 155 width -- int 156 """ 157 super().__init__(**kwargs) 158 if self._do_init and height is not None: 159 for _i in range(height): 160 row = Row(width=width) 161 self.append(row)
Create a group of rows, optionnaly filled with "height" number of rows, of "width" cells each.
Row group bear style information applied to a series of rows.
Arguments:
height -- int
width -- int
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
32class Section(Element): 33 """ODF section "text:section" 34 35 Arguments: 36 37 style -- str 38 39 name -- str 40 """ 41 42 _tag = "text:section" 43 _properties = ( 44 PropDef("style", "text:style-name"), 45 PropDef("name", "text:name"), 46 ) 47 48 def __init__( 49 self, 50 style: str | None = None, 51 name: str | None = None, 52 **kwargs: Any, 53 ) -> None: 54 super().__init__(**kwargs) 55 if self._do_init: 56 if style: 57 self.style = style 58 if name: 59 self.name = name 60 61 def get_formatted_text(self, context: dict | None = None) -> str: 62 result = [element.get_formatted_text(context) for element in self.children] 63 result.append("\n") 64 return "".join(result)
ODF section "text:section"
Arguments:
style -- str
name -- str
61 def get_formatted_text(self, context: dict | None = None) -> str: 62 result = [element.get_formatted_text(context) for element in self.children] 63 result.append("\n") 64 return "".join(result)
This function should return a beautiful version of the text.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
169class Spacer(Element): 170 """This element shall be used to represent the second and all following “ “ 171 (U+0020, SPACE) characters in a sequence of “ “ (U+0020, SPACE) characters. 172 Note: It is not an error if the character preceding the element is not a 173 white space character, but it is good practice to use this element only for 174 the second and all following SPACE characters in a sequence. 175 """ 176 177 _tag = "text:s" 178 _properties: tuple[PropDef, ...] = (PropDef("number", "text:c"),) 179 180 def __init__(self, number: int = 1, **kwargs: Any): 181 """ 182 Arguments: 183 184 number -- int 185 """ 186 super().__init__(**kwargs) 187 if self._do_init: 188 self.number = str(number)
This element shall be used to represent the second and all following “ “ (U+0020, SPACE) characters in a sequence of “ “ (U+0020, SPACE) characters. Note: It is not an error if the character preceding the element is not a white space character, but it is good practice to use this element only for the second and all following SPACE characters in a sequence.
180 def __init__(self, number: int = 1, **kwargs: Any): 181 """ 182 Arguments: 183 184 number -- int 185 """ 186 super().__init__(**kwargs) 187 if self._do_init: 188 self.number = str(number)
Arguments:
number -- int
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
799class Span(Paragraph): 800 """Create a span element "text:span" of the given style containing the optional 801 given text. 802 """ 803 804 _tag = "text:span" 805 _properties = ( 806 PropDef("style", "text:style-name"), 807 PropDef("class_names", "text:class-names"), 808 ) 809 810 def __init__( 811 self, 812 text: str | None = None, 813 style: str | None = None, 814 **kwargs: Any, 815 ) -> None: 816 """ 817 Arguments: 818 819 text -- str 820 821 style -- str 822 """ 823 super().__init__(**kwargs) 824 if self._do_init: 825 if text: 826 self.text = text 827 if style: 828 self.style = style
Create a span element "text:span" of the given style containing the optional given text.
810 def __init__( 811 self, 812 text: str | None = None, 813 style: str | None = None, 814 **kwargs: Any, 815 ) -> None: 816 """ 817 Arguments: 818 819 text -- str 820 821 style -- str 822 """ 823 super().__init__(**kwargs) 824 if self._do_init: 825 if text: 826 self.text = text 827 if style: 828 self.style = style
Arguments:
text -- str
style -- str
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Paragraph
- insert_note
- insert_annotation
- insert_annotation_end
- set_reference_mark
- set_reference_mark_end
- insert_variable
- set_span
- remove_spans
- remove_span
- set_link
- remove_links
- remove_link
- insert_reference
- set_bookmark
- odfdo.paragraph_base.ParagraphBase
- get_formatted_text
- append_plain_text
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
285class Style(Element): 286 """Style class for all these tags: 287 288 'style:style' 289 'number:date-style', 290 'number:number-style', 291 'number:percentage-style', 292 'number:time-style' 293 'style:font-face', 294 'style:master-page', 295 'style:page-layout', 296 'style:presentation-page-layout', 297 'text:list-style', 298 'text:outline-style', 299 'style:tab-stops', 300 ... 301 """ 302 303 _properties: tuple[PropDef, ...] = ( 304 PropDef("page_layout", "style:page-layout-name", "master-page"), 305 PropDef("next_style", "style:next-style-name", "master-page"), 306 PropDef("name", "style:name"), 307 PropDef("parent_style", "style:parent-style-name"), 308 PropDef("display_name", "style:display-name"), 309 PropDef("svg_font_family", "svg:font-family"), 310 PropDef("font_family_generic", "style:font-family-generic"), 311 PropDef("font_pitch", "style:font-pitch"), 312 PropDef("text_style", "text:style-name"), 313 PropDef("master_page", "style:master-page-name", "paragraph"), 314 PropDef("master_page", "style:master-page-name", "paragraph"), 315 PropDef("master_page", "style:master-page-name", "paragraph"), 316 # style:tab-stop 317 PropDef("style_type", "style:type"), 318 PropDef("leader_style", "style:leader-style"), 319 PropDef("leader_text", "style:leader-text"), 320 PropDef("style_position", "style:position"), 321 PropDef("leader_text", "style:position"), 322 ) 323 324 def __init__( # noqa: C901 325 self, 326 family: str | None = None, 327 name: str | None = None, 328 display_name: str | None = None, 329 parent_style: str | None = None, 330 # Where properties apply 331 area: str | None = None, 332 # For family 'text': 333 color: str | tuple | None = None, 334 background_color: str | tuple | None = None, 335 italic: bool = False, 336 bold: bool = False, 337 # For family 'paragraph' 338 master_page: str | None = None, 339 # For family 'master-page' 340 page_layout: str | None = None, 341 next_style: str | None = None, 342 # For family 'table-cell' 343 data_style: str | None = None, # unused 344 border: str | None = None, 345 border_top: str | None = None, 346 border_right: str | None = None, 347 border_bottom: str | None = None, 348 border_left: str | None = None, 349 padding: str | None = None, 350 padding_top: str | None = None, 351 padding_bottom: str | None = None, 352 padding_left: str | None = None, 353 padding_right: str | None = None, 354 shadow: str | None = None, 355 # For family 'table-row' 356 height: str | None = None, 357 use_optimal_height: bool = False, 358 # For family 'table-column' 359 width: str | None = None, 360 break_before: str | None = None, 361 break_after: str | None = None, 362 # For family 'graphic' 363 min_height: str | None = None, 364 # For family 'font-face' 365 font_name: str | None = None, 366 font_family: str | None = None, 367 font_family_generic: str | None = None, 368 font_pitch: str = "variable", 369 # Every other property 370 **kwargs: Any, 371 ) -> None: 372 """Create a style of the given family. The name is not mandatory at this 373 point but will become required when inserting in a document as a common 374 style. 375 376 The display name is the name the user sees in an office application. 377 378 The parent_style is the name of the style this style will inherit from. 379 380 To set properties, pass them as keyword arguments. The area properties 381 apply to is optional and defaults to the family. 382 383 Arguments: 384 385 family -- 'paragraph', 'text', 'section', 'table', 'table-column', 386 'table-row', 'table-cell', 'table-page', 'chart', 387 'drawing-page', 'graphic', 'presentation', 388 'control', 'ruby', 'list', 'number', 'page-layout' 389 'font-face', or 'master-page' 390 391 name -- str 392 393 display_name -- str 394 395 parent_style -- str 396 397 area -- str 398 399 'text' Properties: 400 401 italic -- bool 402 403 bold -- bool 404 405 'paragraph' Properties: 406 407 master_page -- str 408 409 'master-page' Properties: 410 411 page_layout -- str 412 413 next_style -- str 414 415 'table-cell' Properties: 416 417 border, border_top, border_right, border_bottom, border_left -- str, 418 e.g. "0.002cm solid #000000" or 'none' 419 420 padding, padding_top, padding_right, padding_bottom, padding_left -- str, 421 e.g. "0.002cm" or 'none' 422 423 shadow -- str, e.g. "#808080 0.176cm 0.176cm" 424 425 'table-row' Properties: 426 427 height -- str, e.g. '5cm' 428 429 use_optimal_height -- bool 430 431 'table-column' Properties: 432 433 width -- str, e.g. '5cm' 434 435 break_before -- 'page', 'column' or 'auto' 436 437 break_after -- 'page', 'column' or 'auto' 438 """ 439 self._family: str | None = None 440 tag_or_elem = kwargs.get("tag_or_elem", None) 441 if tag_or_elem is None: 442 family = to_str(family) 443 if family not in FAMILY_MAPPING: 444 raise ValueError("Unknown family value: %s" % family) 445 kwargs["tag"] = FAMILY_MAPPING[family] 446 super().__init__(**kwargs) 447 if self._do_init and family not in SUBCLASSED_STYLES: 448 kwargs.pop("tag", None) 449 kwargs.pop("tag_or_elem", None) 450 self.family = family # relevant test made by property 451 # Common attributes 452 if name: 453 self.name = name 454 if display_name: 455 self.display_name = display_name 456 if parent_style: 457 self.parent_style = parent_style 458 # Paragraph 459 if family == "paragraph": 460 if master_page: 461 self.master_page = master_page 462 # Master Page 463 elif family == "master-page": 464 if page_layout: 465 self.page_layout = page_layout 466 if next_style: 467 self.next_style = next_style 468 # Font face 469 elif family == "font-face": 470 if not font_name: 471 raise ValueError("A font_name is required for 'font-face' style") 472 self.set_font( 473 font_name, 474 family=font_family, 475 family_generic=font_family_generic, 476 pitch=font_pitch, 477 ) 478 # Properties 479 if area is None: 480 area = family 481 area = to_str(area) 482 # Text 483 if area == "text": 484 if color: 485 kwargs["fo:color"] = color 486 if background_color: 487 kwargs["fo:background-color"] = background_color 488 if italic: 489 kwargs["fo:font-style"] = "italic" 490 kwargs["style:font-style-asian"] = "italic" 491 kwargs["style:font-style-complex"] = "italic" 492 if bold: 493 kwargs["fo:font-weight"] = "bold" 494 kwargs["style:font-weight-asian"] = "bold" 495 kwargs["style:font-weight-complex"] = "bold" 496 # Table cell 497 elif area == "table-cell": 498 if border: 499 kwargs["fo:border"] = border 500 elif border_top or border_right or border_bottom or border_left: 501 kwargs["fo:border-top"] = border_top or "none" 502 kwargs["fo:border-right"] = border_right or "none" 503 kwargs["fo:border-bottom"] = border_bottom or "none" 504 kwargs["fo:border-left"] = border_left or "none" 505 else: # no border_top, ... neither border are defined 506 pass # left untouched 507 if padding: 508 kwargs["fo:padding"] = padding 509 elif padding_top or padding_right or padding_bottom or padding_left: 510 kwargs["fo:padding-top"] = padding_top or "none" 511 kwargs["fo:padding-right"] = padding_right or "none" 512 kwargs["fo:padding-bottom"] = padding_bottom or "none" 513 kwargs["fo:padding-left"] = padding_left or "none" 514 else: # no border_top, ... neither border are defined 515 pass # left untouched 516 if shadow: 517 kwargs["style:shadow"] = shadow 518 if background_color: 519 kwargs["fo:background-color"] = background_color 520 # Table row 521 elif area == "table-row": 522 if height: 523 kwargs["style:row-height"] = height 524 if use_optimal_height: 525 kwargs["style:use-optimal-row-height"] = Boolean.encode( 526 use_optimal_height 527 ) 528 if background_color: 529 kwargs["fo:background-color"] = background_color 530 # Table column 531 elif area == "table-column": 532 if width: 533 kwargs["style:column-width"] = width 534 if break_before: 535 kwargs["fo:break-before"] = break_before 536 if break_after: 537 kwargs["fo:break-after"] = break_after 538 # Graphic 539 elif area == "graphic": 540 if min_height: 541 kwargs["fo:min-height"] = min_height 542 # Every other properties 543 if kwargs: 544 self.set_properties(kwargs, area=area) 545 546 @property 547 def family(self) -> str | None: 548 if self._family is None: 549 self._family = FALSE_FAMILY_MAP_REVERSE.get( 550 self.tag, self.get_attribute_string("style:family") 551 ) 552 return self._family 553 554 @family.setter 555 def family(self, family: str | None) -> None: 556 self._family = family 557 if family in FAMILY_ODF_STD and self.tag == "style:style": 558 self.set_attribute("style:family", family) 559 560 def get_properties(self, area: str | None = None) -> dict[str, str | dict] | None: 561 """Get the mapping of all properties of this style. By default the 562 properties of the same family, e.g. a paragraph style and its 563 paragraph properties. Specify the area to get the text properties of 564 a paragraph style for example. 565 566 Arguments: 567 568 area -- str 569 570 Return: dict 571 """ 572 if area is None: 573 area = self.family 574 element = self.get_element(f"style:{area}-properties") 575 if element is None: 576 return None 577 properties: dict[str, str | dict] = element.attributes # type: ignore 578 # Nested properties are nested dictionaries 579 for child in element.children: 580 properties[child.tag] = child.attributes 581 return properties 582 583 def set_properties( # noqa: C901 584 self, 585 properties: dict[str, str | dict] | None = None, 586 style: Style | None = None, 587 area: str | None = None, 588 **kwargs: Any, 589 ) -> None: 590 """Set the properties of the "area" type of this style. Properties 591 are given either as a dict or as named arguments (or both). The area 592 is identical to the style family by default. If the properties 593 element is missing, it is created. 594 595 Instead of properties, you can pass a style with properties of the 596 same area. These will be copied. 597 598 Arguments: 599 600 properties -- dict 601 602 style -- Style 603 604 area -- 'paragraph', 'text'... 605 """ 606 if properties is None: 607 properties = {} 608 if area is None: 609 if isinstance(self.family, bool): 610 area = None 611 else: 612 area = self.family 613 element = self.get_element(f"style:{area}-properties") 614 if element is None: 615 element = Element.from_tag(f"style:{area}-properties") 616 self.append(element) 617 if properties or kwargs: 618 properties = _expand_properties_dict(_merge_dicts(properties, kwargs)) 619 elif style is not None: 620 properties = style.get_properties(area=area) 621 if properties is None: 622 return 623 if properties is None: 624 return 625 for key, value in properties.items(): 626 if value is None: 627 element.del_attribute(key) 628 elif isinstance(value, (str, bool, tuple)): 629 element.set_attribute(key, value) 630 else: 631 pass 632 633 def del_properties( 634 self, 635 properties: list[str] | None = None, 636 area: str | None = None, 637 ) -> None: 638 """Delete the given properties, either by list argument or 639 positional argument (or both). Remove only from the given area, 640 identical to the style family by default. 641 642 Arguments: 643 644 properties -- list 645 646 area -- str 647 """ 648 if properties is None: 649 properties = [] 650 if area is None: 651 area = self.family 652 element = self.get_element(f"style:{area}-properties") 653 if element is None: 654 raise ValueError( 655 f"properties element is inexistent for: style:{area}-properties" 656 ) 657 for key in _expand_properties_list(properties): 658 element.del_attribute(key) 659 660 def set_background( # noqa: C901 661 self, 662 color: str | None = None, 663 url: str | None = None, 664 position: str | None = "center", 665 repeat: str | None = None, 666 opacity: str | None = None, 667 filter: str | None = None, # noqa: A002 668 ) -> None: 669 """Set the background color of a text style, or the background color 670 or image of a paragraph style or page layout. 671 672 With no argument, remove any existing background. 673 674 The position is one or two of 'center', 'left', 'right', 'top' or 675 'bottom'. 676 677 The repeat is 'no-repeat', 'repeat' or 'stretch'. 678 679 The opacity is a percentage integer (not a string with the '%s' sign) 680 681 The filter is an application-specific filter name defined elsewhere. 682 683 Though this method is defined on the base style class, it will raise 684 an error if the style type is not compatible. 685 686 Arguments: 687 688 color -- '#rrggbb' 689 690 url -- str 691 692 position -- str 693 694 repeat -- str 695 696 opacity -- int 697 698 filter -- str 699 """ 700 family = self.family 701 if family not in { 702 "text", 703 "paragraph", 704 "page-layout", 705 "section", 706 "table", 707 "table-row", 708 "table-cell", 709 "graphic", 710 }: 711 raise TypeError("No background support for this family") 712 if url is not None and family == "text": 713 raise TypeError("No background image for text styles") 714 properties = self.get_element(f"style:{family}-properties") 715 bg_image: BackgroundImage | None = None 716 if properties is not None: 717 bg_image = properties.get_element("style:background-image") # type:ignore 718 # Erasing 719 if color is None and url is None: 720 if properties is None: 721 return 722 properties.del_attribute("fo:background-color") 723 if bg_image is not None: 724 properties.delete(bg_image) 725 return 726 # Add the properties if necessary 727 if properties is None: 728 properties = Element.from_tag(f"style:{family}-properties") 729 self.append(properties) 730 # Add the color... 731 if color: 732 properties.set_attribute("fo:background-color", color) 733 if bg_image is not None: 734 properties.delete(bg_image) 735 # ... or the background 736 elif url: 737 properties.set_attribute("fo:background-color", "transparent") 738 if bg_image is None: 739 bg_image = Element.from_tag("style:background-image") # type:ignore 740 properties.append(bg_image) # type:ignore 741 bg_image.url = url # type:ignore 742 if position: 743 bg_image.position = position # type:ignore 744 if repeat: 745 bg_image.repeat = repeat # type:ignore 746 if opacity: 747 bg_image.opacity = opacity # type:ignore 748 if filter: 749 bg_image.filter = filter # type:ignore 750 751 # list-style only: 752 753 def get_level_style(self, level: int) -> Style | None: 754 if self.family != "list": 755 return None 756 level_styles = ( 757 "(text:list-level-style-number" 758 "|text:list-level-style-bullet" 759 "|text:list-level-style-image)" 760 ) 761 return self._filtered_element(level_styles, 0, level=level) # type: ignore 762 763 def set_level_style( # noqa: C901 764 self, 765 level: int, 766 num_format: str | None = None, 767 bullet_char: str | None = None, 768 url: str | None = None, 769 display_levels: int | None = None, 770 prefix: str | None = None, 771 suffix: str | None = None, 772 start_value: int | None = None, 773 style: str | None = None, 774 clone: Style | None = None, 775 ) -> Style | None: 776 """ 777 Arguments: 778 779 level -- int 780 781 num_format (for number) -- int 782 783 bullet_char (for bullet) -- str 784 785 url (for image) -- str 786 787 display_levels -- int 788 789 prefix -- str 790 791 suffix -- str 792 793 start_value -- int 794 795 style -- str 796 797 clone -- List Style 798 799 Return: 800 level_style created 801 """ 802 if self.family != "list": 803 return None 804 # Expected name 805 if num_format is not None: 806 level_style_name = "text:list-level-style-number" 807 elif bullet_char is not None: 808 level_style_name = "text:list-level-style-bullet" 809 elif url is not None: 810 level_style_name = "text:list-level-style-image" 811 elif clone is not None: 812 level_style_name = clone.tag 813 else: 814 raise ValueError("unknown level style type") 815 was_created = False 816 # Cloning or reusing an existing element 817 level_style: Style | None = None 818 if clone is not None: 819 level_style = clone.clone # type: ignore 820 was_created = True 821 else: 822 level_style = self.get_level_style(level) 823 if level_style is None: 824 level_style = Element.from_tag(level_style_name) # type: ignore 825 was_created = True 826 if level_style is None: 827 return None 828 # Transmute if the type changed 829 if level_style.tag != level_style_name: 830 print("Warn: different style", level_style_name, level_style.tag) 831 level_style.tag = level_style_name 832 # Set the level 833 level_style.set_attribute("text:level", str(level)) 834 # Set the main attribute 835 if num_format is not None: 836 level_style.set_attribute("fo:num-format", num_format) 837 elif bullet_char is not None: 838 level_style.set_attribute("text:bullet-char", bullet_char) 839 elif url is not None: 840 level_style.set_attribute("xlink:href", url) 841 # Set attributes 842 if prefix: 843 level_style.set_attribute("style:num-prefix", prefix) 844 if suffix: 845 level_style.set_attribute("style:num-suffix", suffix) 846 if display_levels: 847 level_style.set_attribute("text:display-levels", str(display_levels)) 848 if start_value: 849 level_style.set_attribute("text:start-value", str(start_value)) 850 if style: 851 level_style.text_style = style # type: ignore 852 # Commit the creation 853 if was_created: 854 self.append(level_style) 855 return level_style 856 857 # page-layout only: 858 859 def get_header_style(self) -> Element | None: 860 if self.family != "page-layout": 861 return None 862 return self.get_element("style:header-style") 863 864 def set_header_style(self, new_style: Style) -> None: 865 if self.family != "page-layout": 866 return 867 header_style = self.get_header_style() 868 if header_style is not None: 869 self.delete(header_style) 870 self.append(new_style) 871 872 def get_footer_style(self) -> Style | None: 873 if self.family != "page-layout": 874 return None 875 return self.get_element("style:footer-style") # type: ignore 876 877 def set_footer_style(self, new_style: Style) -> None: 878 if self.family != "page-layout": 879 return 880 footer_style = self.get_footer_style() 881 if footer_style is not None: 882 self.delete(footer_style) 883 self.append(new_style) 884 885 # master-page only: 886 887 def _set_header_or_footer( 888 self, 889 text_or_element: str | Element | list[Element | str], 890 name: str = "header", 891 style: str = "Header", 892 ) -> None: 893 if name == "header": 894 header_or_footer = self.get_page_header() 895 else: 896 header_or_footer = self.get_page_footer() 897 if header_or_footer is None: 898 header_or_footer = Element.from_tag("style:" + name) 899 self.append(header_or_footer) 900 else: 901 header_or_footer.clear() 902 if ( 903 isinstance(text_or_element, Element) 904 and text_or_element.tag == f"style:{name}" 905 ): 906 # Already a header or footer? 907 self.delete(header_or_footer) 908 self.append(text_or_element) 909 return 910 if isinstance(text_or_element, (Element, str)): 911 elem_list: list[Element | str] = [text_or_element] 912 else: 913 elem_list = text_or_element 914 for item in elem_list: 915 if isinstance(item, str): 916 paragraph = Element.from_tag("text:p") 917 paragraph.style = style # type: ignore 918 header_or_footer.append(paragraph) 919 elif isinstance(item, Element): 920 header_or_footer.append(item) 921 922 def get_page_header(self) -> Element | None: 923 """Get the element that contains the header contents. 924 925 If None, no header was set. 926 """ 927 if self.family != "master-page": 928 return None 929 return self.get_element("style:header") 930 931 def set_page_header( 932 self, 933 text_or_element: str | Element | list[Element | str], 934 ) -> None: 935 """Create or replace the header by the given content. It can already 936 be a complete header. 937 938 If you only want to update the existing header, get it and use the 939 API. 940 941 Arguments: 942 943 text_or_element -- str or Element or a list of them 944 """ 945 if self.family != "master-page": 946 return None 947 self._set_header_or_footer(text_or_element) 948 949 def get_page_footer(self) -> Element | None: 950 """Get the element that contains the footer contents. 951 952 If None, no footer was set. 953 """ 954 if self.family != "master-page": 955 return None 956 return self.get_element("style:footer") 957 958 def set_page_footer( 959 self, 960 text_or_element: str | Element | list[Element | str], 961 ) -> None: 962 """Create or replace the footer by the given content. It can already 963 be a complete footer. 964 965 If you only want to update the existing footer, get it and use the 966 API. 967 968 Arguments: 969 970 text_or_element -- str or Element or a list of them 971 """ 972 if self.family != "master-page": 973 return None 974 self._set_header_or_footer(text_or_element, name="footer", style="Footer") 975 976 # font-face only: 977 978 def set_font( 979 self, 980 name: str, 981 family: str | None = None, 982 family_generic: str | None = None, 983 pitch: str = "variable", 984 ) -> None: 985 if self.family != "font-face": 986 return 987 self.name = name 988 if family is None: 989 family = name 990 self.svg_font_family = f'"{family}"' 991 if family_generic is not None: 992 self.font_family_generic = family_generic 993 self.font_pitch = pitch
Style class for all these tags:
'style:style' 'number:date-style', 'number:number-style', 'number:percentage-style', 'number:time-style' 'style:font-face', 'style:master-page', 'style:page-layout', 'style:presentation-page-layout', 'text:list-style', 'text:outline-style', 'style:tab-stops', ...
324 def __init__( # noqa: C901 325 self, 326 family: str | None = None, 327 name: str | None = None, 328 display_name: str | None = None, 329 parent_style: str | None = None, 330 # Where properties apply 331 area: str | None = None, 332 # For family 'text': 333 color: str | tuple | None = None, 334 background_color: str | tuple | None = None, 335 italic: bool = False, 336 bold: bool = False, 337 # For family 'paragraph' 338 master_page: str | None = None, 339 # For family 'master-page' 340 page_layout: str | None = None, 341 next_style: str | None = None, 342 # For family 'table-cell' 343 data_style: str | None = None, # unused 344 border: str | None = None, 345 border_top: str | None = None, 346 border_right: str | None = None, 347 border_bottom: str | None = None, 348 border_left: str | None = None, 349 padding: str | None = None, 350 padding_top: str | None = None, 351 padding_bottom: str | None = None, 352 padding_left: str | None = None, 353 padding_right: str | None = None, 354 shadow: str | None = None, 355 # For family 'table-row' 356 height: str | None = None, 357 use_optimal_height: bool = False, 358 # For family 'table-column' 359 width: str | None = None, 360 break_before: str | None = None, 361 break_after: str | None = None, 362 # For family 'graphic' 363 min_height: str | None = None, 364 # For family 'font-face' 365 font_name: str | None = None, 366 font_family: str | None = None, 367 font_family_generic: str | None = None, 368 font_pitch: str = "variable", 369 # Every other property 370 **kwargs: Any, 371 ) -> None: 372 """Create a style of the given family. The name is not mandatory at this 373 point but will become required when inserting in a document as a common 374 style. 375 376 The display name is the name the user sees in an office application. 377 378 The parent_style is the name of the style this style will inherit from. 379 380 To set properties, pass them as keyword arguments. The area properties 381 apply to is optional and defaults to the family. 382 383 Arguments: 384 385 family -- 'paragraph', 'text', 'section', 'table', 'table-column', 386 'table-row', 'table-cell', 'table-page', 'chart', 387 'drawing-page', 'graphic', 'presentation', 388 'control', 'ruby', 'list', 'number', 'page-layout' 389 'font-face', or 'master-page' 390 391 name -- str 392 393 display_name -- str 394 395 parent_style -- str 396 397 area -- str 398 399 'text' Properties: 400 401 italic -- bool 402 403 bold -- bool 404 405 'paragraph' Properties: 406 407 master_page -- str 408 409 'master-page' Properties: 410 411 page_layout -- str 412 413 next_style -- str 414 415 'table-cell' Properties: 416 417 border, border_top, border_right, border_bottom, border_left -- str, 418 e.g. "0.002cm solid #000000" or 'none' 419 420 padding, padding_top, padding_right, padding_bottom, padding_left -- str, 421 e.g. "0.002cm" or 'none' 422 423 shadow -- str, e.g. "#808080 0.176cm 0.176cm" 424 425 'table-row' Properties: 426 427 height -- str, e.g. '5cm' 428 429 use_optimal_height -- bool 430 431 'table-column' Properties: 432 433 width -- str, e.g. '5cm' 434 435 break_before -- 'page', 'column' or 'auto' 436 437 break_after -- 'page', 'column' or 'auto' 438 """ 439 self._family: str | None = None 440 tag_or_elem = kwargs.get("tag_or_elem", None) 441 if tag_or_elem is None: 442 family = to_str(family) 443 if family not in FAMILY_MAPPING: 444 raise ValueError("Unknown family value: %s" % family) 445 kwargs["tag"] = FAMILY_MAPPING[family] 446 super().__init__(**kwargs) 447 if self._do_init and family not in SUBCLASSED_STYLES: 448 kwargs.pop("tag", None) 449 kwargs.pop("tag_or_elem", None) 450 self.family = family # relevant test made by property 451 # Common attributes 452 if name: 453 self.name = name 454 if display_name: 455 self.display_name = display_name 456 if parent_style: 457 self.parent_style = parent_style 458 # Paragraph 459 if family == "paragraph": 460 if master_page: 461 self.master_page = master_page 462 # Master Page 463 elif family == "master-page": 464 if page_layout: 465 self.page_layout = page_layout 466 if next_style: 467 self.next_style = next_style 468 # Font face 469 elif family == "font-face": 470 if not font_name: 471 raise ValueError("A font_name is required for 'font-face' style") 472 self.set_font( 473 font_name, 474 family=font_family, 475 family_generic=font_family_generic, 476 pitch=font_pitch, 477 ) 478 # Properties 479 if area is None: 480 area = family 481 area = to_str(area) 482 # Text 483 if area == "text": 484 if color: 485 kwargs["fo:color"] = color 486 if background_color: 487 kwargs["fo:background-color"] = background_color 488 if italic: 489 kwargs["fo:font-style"] = "italic" 490 kwargs["style:font-style-asian"] = "italic" 491 kwargs["style:font-style-complex"] = "italic" 492 if bold: 493 kwargs["fo:font-weight"] = "bold" 494 kwargs["style:font-weight-asian"] = "bold" 495 kwargs["style:font-weight-complex"] = "bold" 496 # Table cell 497 elif area == "table-cell": 498 if border: 499 kwargs["fo:border"] = border 500 elif border_top or border_right or border_bottom or border_left: 501 kwargs["fo:border-top"] = border_top or "none" 502 kwargs["fo:border-right"] = border_right or "none" 503 kwargs["fo:border-bottom"] = border_bottom or "none" 504 kwargs["fo:border-left"] = border_left or "none" 505 else: # no border_top, ... neither border are defined 506 pass # left untouched 507 if padding: 508 kwargs["fo:padding"] = padding 509 elif padding_top or padding_right or padding_bottom or padding_left: 510 kwargs["fo:padding-top"] = padding_top or "none" 511 kwargs["fo:padding-right"] = padding_right or "none" 512 kwargs["fo:padding-bottom"] = padding_bottom or "none" 513 kwargs["fo:padding-left"] = padding_left or "none" 514 else: # no border_top, ... neither border are defined 515 pass # left untouched 516 if shadow: 517 kwargs["style:shadow"] = shadow 518 if background_color: 519 kwargs["fo:background-color"] = background_color 520 # Table row 521 elif area == "table-row": 522 if height: 523 kwargs["style:row-height"] = height 524 if use_optimal_height: 525 kwargs["style:use-optimal-row-height"] = Boolean.encode( 526 use_optimal_height 527 ) 528 if background_color: 529 kwargs["fo:background-color"] = background_color 530 # Table column 531 elif area == "table-column": 532 if width: 533 kwargs["style:column-width"] = width 534 if break_before: 535 kwargs["fo:break-before"] = break_before 536 if break_after: 537 kwargs["fo:break-after"] = break_after 538 # Graphic 539 elif area == "graphic": 540 if min_height: 541 kwargs["fo:min-height"] = min_height 542 # Every other properties 543 if kwargs: 544 self.set_properties(kwargs, area=area)
Create a style of the given family. The name is not mandatory at this point but will become required when inserting in a document as a common style.
The display name is the name the user sees in an office application.
The parent_style is the name of the style this style will inherit from.
To set properties, pass them as keyword arguments. The area properties apply to is optional and defaults to the family.
Arguments:
family -- 'paragraph', 'text', 'section', 'table', 'table-column',
'table-row', 'table-cell', 'table-page', 'chart',
'drawing-page', 'graphic', 'presentation',
'control', 'ruby', 'list', 'number', 'page-layout'
'font-face', or 'master-page'
name -- str
display_name -- str
parent_style -- str
area -- str
'text' Properties:
italic -- bool
bold -- bool
'paragraph' Properties:
master_page -- str
'master-page' Properties:
page_layout -- str
next_style -- str
'table-cell' Properties:
border, border_top, border_right, border_bottom, border_left -- str,
e.g. "0.002cm solid #000000" or 'none'
padding, padding_top, padding_right, padding_bottom, padding_left -- str,
e.g. "0.002cm" or 'none'
shadow -- str, e.g. "#808080 0.176cm 0.176cm"
'table-row' Properties:
height -- str, e.g. '5cm'
use_optimal_height -- bool
'table-column' Properties:
width -- str, e.g. '5cm'
break_before -- 'page', 'column' or 'auto'
break_after -- 'page', 'column' or 'auto'
560 def get_properties(self, area: str | None = None) -> dict[str, str | dict] | None: 561 """Get the mapping of all properties of this style. By default the 562 properties of the same family, e.g. a paragraph style and its 563 paragraph properties. Specify the area to get the text properties of 564 a paragraph style for example. 565 566 Arguments: 567 568 area -- str 569 570 Return: dict 571 """ 572 if area is None: 573 area = self.family 574 element = self.get_element(f"style:{area}-properties") 575 if element is None: 576 return None 577 properties: dict[str, str | dict] = element.attributes # type: ignore 578 # Nested properties are nested dictionaries 579 for child in element.children: 580 properties[child.tag] = child.attributes 581 return properties
Get the mapping of all properties of this style. By default the properties of the same family, e.g. a paragraph style and its paragraph properties. Specify the area to get the text properties of a paragraph style for example.
Arguments:
area -- str
Return: dict
583 def set_properties( # noqa: C901 584 self, 585 properties: dict[str, str | dict] | None = None, 586 style: Style | None = None, 587 area: str | None = None, 588 **kwargs: Any, 589 ) -> None: 590 """Set the properties of the "area" type of this style. Properties 591 are given either as a dict or as named arguments (or both). The area 592 is identical to the style family by default. If the properties 593 element is missing, it is created. 594 595 Instead of properties, you can pass a style with properties of the 596 same area. These will be copied. 597 598 Arguments: 599 600 properties -- dict 601 602 style -- Style 603 604 area -- 'paragraph', 'text'... 605 """ 606 if properties is None: 607 properties = {} 608 if area is None: 609 if isinstance(self.family, bool): 610 area = None 611 else: 612 area = self.family 613 element = self.get_element(f"style:{area}-properties") 614 if element is None: 615 element = Element.from_tag(f"style:{area}-properties") 616 self.append(element) 617 if properties or kwargs: 618 properties = _expand_properties_dict(_merge_dicts(properties, kwargs)) 619 elif style is not None: 620 properties = style.get_properties(area=area) 621 if properties is None: 622 return 623 if properties is None: 624 return 625 for key, value in properties.items(): 626 if value is None: 627 element.del_attribute(key) 628 elif isinstance(value, (str, bool, tuple)): 629 element.set_attribute(key, value) 630 else: 631 pass
Set the properties of the "area" type of this style. Properties are given either as a dict or as named arguments (or both). The area is identical to the style family by default. If the properties element is missing, it is created.
Instead of properties, you can pass a style with properties of the same area. These will be copied.
Arguments:
properties -- dict
style -- Style
area -- 'paragraph', 'text'...
633 def del_properties( 634 self, 635 properties: list[str] | None = None, 636 area: str | None = None, 637 ) -> None: 638 """Delete the given properties, either by list argument or 639 positional argument (or both). Remove only from the given area, 640 identical to the style family by default. 641 642 Arguments: 643 644 properties -- list 645 646 area -- str 647 """ 648 if properties is None: 649 properties = [] 650 if area is None: 651 area = self.family 652 element = self.get_element(f"style:{area}-properties") 653 if element is None: 654 raise ValueError( 655 f"properties element is inexistent for: style:{area}-properties" 656 ) 657 for key in _expand_properties_list(properties): 658 element.del_attribute(key)
Delete the given properties, either by list argument or positional argument (or both). Remove only from the given area, identical to the style family by default.
Arguments:
properties -- list
area -- str
660 def set_background( # noqa: C901 661 self, 662 color: str | None = None, 663 url: str | None = None, 664 position: str | None = "center", 665 repeat: str | None = None, 666 opacity: str | None = None, 667 filter: str | None = None, # noqa: A002 668 ) -> None: 669 """Set the background color of a text style, or the background color 670 or image of a paragraph style or page layout. 671 672 With no argument, remove any existing background. 673 674 The position is one or two of 'center', 'left', 'right', 'top' or 675 'bottom'. 676 677 The repeat is 'no-repeat', 'repeat' or 'stretch'. 678 679 The opacity is a percentage integer (not a string with the '%s' sign) 680 681 The filter is an application-specific filter name defined elsewhere. 682 683 Though this method is defined on the base style class, it will raise 684 an error if the style type is not compatible. 685 686 Arguments: 687 688 color -- '#rrggbb' 689 690 url -- str 691 692 position -- str 693 694 repeat -- str 695 696 opacity -- int 697 698 filter -- str 699 """ 700 family = self.family 701 if family not in { 702 "text", 703 "paragraph", 704 "page-layout", 705 "section", 706 "table", 707 "table-row", 708 "table-cell", 709 "graphic", 710 }: 711 raise TypeError("No background support for this family") 712 if url is not None and family == "text": 713 raise TypeError("No background image for text styles") 714 properties = self.get_element(f"style:{family}-properties") 715 bg_image: BackgroundImage | None = None 716 if properties is not None: 717 bg_image = properties.get_element("style:background-image") # type:ignore 718 # Erasing 719 if color is None and url is None: 720 if properties is None: 721 return 722 properties.del_attribute("fo:background-color") 723 if bg_image is not None: 724 properties.delete(bg_image) 725 return 726 # Add the properties if necessary 727 if properties is None: 728 properties = Element.from_tag(f"style:{family}-properties") 729 self.append(properties) 730 # Add the color... 731 if color: 732 properties.set_attribute("fo:background-color", color) 733 if bg_image is not None: 734 properties.delete(bg_image) 735 # ... or the background 736 elif url: 737 properties.set_attribute("fo:background-color", "transparent") 738 if bg_image is None: 739 bg_image = Element.from_tag("style:background-image") # type:ignore 740 properties.append(bg_image) # type:ignore 741 bg_image.url = url # type:ignore 742 if position: 743 bg_image.position = position # type:ignore 744 if repeat: 745 bg_image.repeat = repeat # type:ignore 746 if opacity: 747 bg_image.opacity = opacity # type:ignore 748 if filter: 749 bg_image.filter = filter # type:ignore
Set the background color of a text style, or the background color or image of a paragraph style or page layout.
With no argument, remove any existing background.
The position is one or two of 'center', 'left', 'right', 'top' or 'bottom'.
The repeat is 'no-repeat', 'repeat' or 'stretch'.
The opacity is a percentage integer (not a string with the '%s' sign)
The filter is an application-specific filter name defined elsewhere.
Though this method is defined on the base style class, it will raise an error if the style type is not compatible.
Arguments:
color -- '#rrggbb'
url -- str
position -- str
repeat -- str
opacity -- int
filter -- str
753 def get_level_style(self, level: int) -> Style | None: 754 if self.family != "list": 755 return None 756 level_styles = ( 757 "(text:list-level-style-number" 758 "|text:list-level-style-bullet" 759 "|text:list-level-style-image)" 760 ) 761 return self._filtered_element(level_styles, 0, level=level) # type: ignore
763 def set_level_style( # noqa: C901 764 self, 765 level: int, 766 num_format: str | None = None, 767 bullet_char: str | None = None, 768 url: str | None = None, 769 display_levels: int | None = None, 770 prefix: str | None = None, 771 suffix: str | None = None, 772 start_value: int | None = None, 773 style: str | None = None, 774 clone: Style | None = None, 775 ) -> Style | None: 776 """ 777 Arguments: 778 779 level -- int 780 781 num_format (for number) -- int 782 783 bullet_char (for bullet) -- str 784 785 url (for image) -- str 786 787 display_levels -- int 788 789 prefix -- str 790 791 suffix -- str 792 793 start_value -- int 794 795 style -- str 796 797 clone -- List Style 798 799 Return: 800 level_style created 801 """ 802 if self.family != "list": 803 return None 804 # Expected name 805 if num_format is not None: 806 level_style_name = "text:list-level-style-number" 807 elif bullet_char is not None: 808 level_style_name = "text:list-level-style-bullet" 809 elif url is not None: 810 level_style_name = "text:list-level-style-image" 811 elif clone is not None: 812 level_style_name = clone.tag 813 else: 814 raise ValueError("unknown level style type") 815 was_created = False 816 # Cloning or reusing an existing element 817 level_style: Style | None = None 818 if clone is not None: 819 level_style = clone.clone # type: ignore 820 was_created = True 821 else: 822 level_style = self.get_level_style(level) 823 if level_style is None: 824 level_style = Element.from_tag(level_style_name) # type: ignore 825 was_created = True 826 if level_style is None: 827 return None 828 # Transmute if the type changed 829 if level_style.tag != level_style_name: 830 print("Warn: different style", level_style_name, level_style.tag) 831 level_style.tag = level_style_name 832 # Set the level 833 level_style.set_attribute("text:level", str(level)) 834 # Set the main attribute 835 if num_format is not None: 836 level_style.set_attribute("fo:num-format", num_format) 837 elif bullet_char is not None: 838 level_style.set_attribute("text:bullet-char", bullet_char) 839 elif url is not None: 840 level_style.set_attribute("xlink:href", url) 841 # Set attributes 842 if prefix: 843 level_style.set_attribute("style:num-prefix", prefix) 844 if suffix: 845 level_style.set_attribute("style:num-suffix", suffix) 846 if display_levels: 847 level_style.set_attribute("text:display-levels", str(display_levels)) 848 if start_value: 849 level_style.set_attribute("text:start-value", str(start_value)) 850 if style: 851 level_style.text_style = style # type: ignore 852 # Commit the creation 853 if was_created: 854 self.append(level_style) 855 return level_style
Arguments:
level -- int
num_format (for number) -- int
bullet_char (for bullet) -- str
url (for image) -- str
display_levels -- int
prefix -- str
suffix -- str
start_value -- int
style -- str
clone -- List Style
Return: level_style created
922 def get_page_header(self) -> Element | None: 923 """Get the element that contains the header contents. 924 925 If None, no header was set. 926 """ 927 if self.family != "master-page": 928 return None 929 return self.get_element("style:header")
Get the element that contains the header contents.
If None, no header was set.
931 def set_page_header( 932 self, 933 text_or_element: str | Element | list[Element | str], 934 ) -> None: 935 """Create or replace the header by the given content. It can already 936 be a complete header. 937 938 If you only want to update the existing header, get it and use the 939 API. 940 941 Arguments: 942 943 text_or_element -- str or Element or a list of them 944 """ 945 if self.family != "master-page": 946 return None 947 self._set_header_or_footer(text_or_element)
Create or replace the header by the given content. It can already be a complete header.
If you only want to update the existing header, get it and use the API.
Arguments:
text_or_element -- str or Element or a list of them
978 def set_font( 979 self, 980 name: str, 981 family: str | None = None, 982 family_generic: str | None = None, 983 pitch: str = "variable", 984 ) -> None: 985 if self.family != "font-face": 986 return 987 self.name = name 988 if family is None: 989 family = name 990 self.svg_font_family = f'"{family}"' 991 if family_generic is not None: 992 self.font_family_generic = family_generic 993 self.font_pitch = pitch
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
58class Styles(XmlPart): 59 def _get_style_contexts( 60 self, family: str, automatic: bool = False 61 ) -> list[Element]: 62 if automatic: 63 return [self.get_element("//office:automatic-styles")] 64 if not family: 65 # All possibilities 66 return [ 67 self.get_element("//office:automatic-styles"), 68 self.get_element("//office:styles"), 69 self.get_element("//office:master-styles"), 70 self.get_element("//office:font-face-decls"), 71 ] 72 queries = CONTEXT_MAPPING.get(family) 73 if queries is None: 74 raise ValueError(f"unknown family: {family}") 75 # print('q:', queries) 76 return [self.get_element(query) for query in queries] 77 78 def get_styles(self, family: str = "", automatic: bool = False) -> list[Element]: 79 """Return the list of styles in the Content part, optionally limited 80 to the given family, optionaly limited to automatic styles. 81 82 Arguments: 83 84 family -- str 85 86 automatic -- bool 87 88 Return: list of Style 89 """ 90 result = [] 91 for context in self._get_style_contexts(family, automatic=automatic): 92 if context is None: 93 continue 94 # print('-ctx----', automatic) 95 # print(context.tag) 96 # print(context.__class__) 97 # print(context.serialize()) 98 result.extend(context.get_styles(family=family)) 99 return result 100 101 def get_style( 102 self, 103 family: str, 104 name_or_element: str | Style | None = None, 105 display_name: str | None = None, 106 ) -> Style | None: 107 """Return the style uniquely identified by the name/family pair. If 108 the argument is already a style object, it will return it. 109 110 If the name is None, the default style is fetched. 111 112 If the name is not the internal name but the name you gave in the 113 desktop application, use display_name instead. 114 115 Arguments: 116 117 family -- 'paragraph', 'text', 'graphic', 'table', 'list', 118 'number', 'page-layout', 'master-page' 119 120 name_or_element -- str, odf_style or None 121 122 display_name -- str or None 123 124 Return: odf_style or None if not found 125 """ 126 for context in self._get_style_contexts(family): 127 if context is None: 128 continue 129 style = context.get_style( 130 family, 131 name_or_element=name_or_element, 132 display_name=display_name, 133 ) 134 if style is not None: 135 return style # type: ignore 136 return None 137 138 def get_master_pages(self) -> list[Element]: 139 query = make_xpath_query("descendant::style:master-page") 140 return self.get_elements(query) # type:ignore 141 142 def get_master_page(self, position: int = 0) -> Element | None: 143 results = self.get_master_pages() 144 try: 145 return results[position] 146 except IndexError: 147 return None
Representation of an XML part.
Abstraction of the XML library behind.
78 def get_styles(self, family: str = "", automatic: bool = False) -> list[Element]: 79 """Return the list of styles in the Content part, optionally limited 80 to the given family, optionaly limited to automatic styles. 81 82 Arguments: 83 84 family -- str 85 86 automatic -- bool 87 88 Return: list of Style 89 """ 90 result = [] 91 for context in self._get_style_contexts(family, automatic=automatic): 92 if context is None: 93 continue 94 # print('-ctx----', automatic) 95 # print(context.tag) 96 # print(context.__class__) 97 # print(context.serialize()) 98 result.extend(context.get_styles(family=family)) 99 return result
Return the list of styles in the Content part, optionally limited to the given family, optionaly limited to automatic styles.
Arguments:
family -- str
automatic -- bool
Return: list of Style
101 def get_style( 102 self, 103 family: str, 104 name_or_element: str | Style | None = None, 105 display_name: str | None = None, 106 ) -> Style | None: 107 """Return the style uniquely identified by the name/family pair. If 108 the argument is already a style object, it will return it. 109 110 If the name is None, the default style is fetched. 111 112 If the name is not the internal name but the name you gave in the 113 desktop application, use display_name instead. 114 115 Arguments: 116 117 family -- 'paragraph', 'text', 'graphic', 'table', 'list', 118 'number', 'page-layout', 'master-page' 119 120 name_or_element -- str, odf_style or None 121 122 display_name -- str or None 123 124 Return: odf_style or None if not found 125 """ 126 for context in self._get_style_contexts(family): 127 if context is None: 128 continue 129 style = context.get_style( 130 family, 131 name_or_element=name_or_element, 132 display_name=display_name, 133 ) 134 if style is not None: 135 return style # type: ignore 136 return None
Return the style uniquely identified by the name/family pair. If the argument is already a style object, it will return it.
If the name is None, the default style is fetched.
If the name is not the internal name but the name you gave in the desktop application, use display_name instead.
Arguments:
family -- 'paragraph', 'text', 'graphic', 'table', 'list',
'number', 'page-layout', 'master-page'
name_or_element -- str, odf_style or None
display_name -- str or None
Return: odf_style or None if not found
Inherited Members
168class TOC(Element): 169 """Table of content. 170 The "text:table-of-content" element represents a table of contents for a 171 document. The items that can be listed in a table of contents are: 172 - Headings (as defined by the outline structure of the document), up to 173 a selected level. 174 - Table of contents index marks. 175 - Paragraphs formatted with specified paragraph styles. 176 177 178 Implementation: 179 Default parameters are what most people use: protected from manual 180 modifications and not limited in title levels. 181 182 The name is mandatory and derived automatically from the title if not 183 given. Provide one in case of a conflict with other TOCs in the same 184 document. 185 186 The "text:table-of-content" element has the following attributes: 187 text:name, text:protected, text:protection-key, 188 text:protection-key-digest-algorithm, text:style-name and xml:id. 189 190 Arguments: 191 192 title -- str 193 194 name -- str 195 196 protected -- bool 197 198 outline_level -- int 199 200 style -- str 201 202 title_style -- str 203 204 entry_style -- str 205 """ 206 207 _tag = "text:table-of-content" 208 _properties = ( 209 PropDef("name", "text:name"), 210 PropDef("style", "text:style-name"), 211 PropDef("xml_id", "xml:id"), 212 PropDef("protected", "text:protected"), 213 PropDef("protection_key", "text:protection-key"), 214 PropDef( 215 "protection_key_digest_algorithm", "text:protection-key-digest-algorithm" 216 ), 217 ) 218 219 def __init__( 220 self, 221 title: str = "Table of Contents", 222 name: str | None = None, 223 protected: bool = True, 224 outline_level: int = 0, 225 style: str | None = None, 226 title_style: str = "Contents_20_Heading", 227 entry_style: str = "Contents_20_%d", 228 **kwargs: Any, 229 ) -> None: 230 super().__init__(**kwargs) 231 if self._do_init: 232 if style: 233 self.style = style 234 if protected: 235 self.protected = protected 236 if name is None: 237 self.name = f"{title}1" 238 # Create the source template 239 toc_source = self.create_toc_source( 240 title, outline_level, title_style, entry_style 241 ) 242 self.append(toc_source) 243 # Create the index body automatically with the index title 244 if title: 245 # This style is in the template document 246 self.set_toc_title(title, text_style=title_style) 247 248 @staticmethod 249 def create_toc_source( 250 title: str, 251 outline_level: int, 252 title_style: str, 253 entry_style: str, 254 ) -> Element: 255 toc_source = Element.from_tag("text:table-of-content-source") 256 toc_source.set_attribute("text:outline-level", str(outline_level)) 257 if title: 258 title_template = IndexTitleTemplate() 259 if title_style: 260 # This style is in the template document 261 title_template.style = title_style 262 title_template.text = title 263 toc_source.append(title_template) 264 for level in range(1, 11): 265 template = TocEntryTemplate(outline_level=level) 266 if entry_style: 267 template.style = entry_style % level 268 toc_source.append(template) 269 return toc_source 270 271 def __str__(self) -> str: 272 return self.get_formatted_text() 273 274 def get_formatted_text(self, context: dict | None = None) -> str: 275 index_body = self.get_element("text:index-body") 276 277 if index_body is None: 278 return "" 279 if context is None: 280 context = {} 281 if context.get("rst_mode"): 282 return "\n.. contents::\n\n" 283 284 result = [] 285 for element in index_body.children: 286 if element.tag == "text:index-title": 287 for child_element in element.children: 288 result.append(child_element.get_formatted_text(context).strip()) 289 else: 290 result.append(element.get_formatted_text(context).strip()) 291 return "\n".join(result) 292 293 @property 294 def outline_level(self) -> int | None: 295 source = self.get_element("text:table-of-content-source") 296 if source is None: 297 return None 298 return source.get_attribute_integer("text:outline-level") 299 300 @outline_level.setter 301 def outline_level(self, level: int) -> None: 302 source = self.get_element("text:table-of-content-source") 303 if source is None: 304 source = Element.from_tag("text:table-of-content-source") 305 self.insert(source, FIRST_CHILD) 306 source.set_attribute("text:outline-level", str(level)) 307 308 @property 309 def body(self) -> Element | None: 310 return self.get_element("text:index-body") 311 312 @body.setter 313 def body(self, body: Element | None = None) -> Element | None: 314 old_body = self.body 315 if old_body is not None: 316 self.delete(old_body) 317 if body is None: 318 body = Element.from_tag("text:index-body") 319 self.append(body) 320 return body 321 322 def get_title(self) -> str: 323 index_body = self.body 324 if index_body is None: 325 return "" 326 index_title = index_body.get_element(IndexTitle._tag) 327 if index_title is None: 328 return "" 329 return index_title.text_content 330 331 def set_toc_title( 332 self, 333 title: str, 334 style: str | None = None, 335 text_style: str | None = None, 336 ) -> None: 337 index_body = self.body 338 if index_body is None: 339 self.body = None 340 index_body = self.body 341 index_title = index_body.get_element(IndexTitle._tag) # type: ignore 342 if index_title is None: 343 name = f"{self.name}_Head" 344 index_title = IndexTitle( 345 name=name, style=style, title_text=title, text_style=text_style 346 ) 347 index_body.append(index_title) # type: ignore 348 else: 349 if style: 350 index_title.style = style # type: ignore 351 paragraph = index_title.get_paragraph() 352 if paragraph is None: 353 paragraph = Paragraph() 354 index_title.append(paragraph) 355 if text_style: 356 paragraph.style = text_style # type: ignore 357 paragraph.text = title 358 359 @staticmethod 360 def _header_numbering(level_indexes: dict[int, int], level: int) -> str: 361 """Return the header hierarchical number (like "1.2.3.").""" 362 numbers: list[int] = [] 363 # before header level 364 for idx in range(1, level): 365 numbers.append(level_indexes.setdefault(idx, 1)) 366 # header level 367 index = level_indexes.get(level, 0) + 1 368 level_indexes[level] = index 369 numbers.append(index) 370 # after header level 371 idx = level + 1 372 while idx in level_indexes: 373 del level_indexes[idx] 374 idx += 1 375 return ".".join(str(x) for x in numbers) + "." 376 377 def fill( # noqa: C901 378 self, 379 document: Document | None = None, 380 use_default_styles: bool = True, 381 ) -> None: 382 """Fill the TOC with the titles found in the document. A TOC is not 383 contextual so it will catch all titles before and after its insertion. 384 If the TOC is not attached to a document, attach it beforehand or 385 provide one as argument. 386 387 For having a pretty TOC, let use_default_styles by default. 388 389 Arguments: 390 391 document -- Document 392 393 use_default_styles -- bool 394 """ 395 # Find the body 396 if document is not None: 397 body: Element | None = document.body 398 else: 399 body = self.document_body 400 if body is None: 401 raise ValueError("The TOC must be related to a document somehow") 402 403 # Save the title 404 index_body = self.body 405 title = index_body.get_element("text:index-title") # type: ignore 406 407 # Clean the old index-body 408 self.body = None 409 index_body = self.body 410 411 # Restore the title 412 if title and str(title): 413 index_body.insert(title, position=0) # type: ignore 414 415 # Insert default TOC style 416 if use_default_styles: 417 automatic_styles = body.get_element("//office:automatic-styles") 418 if isinstance(automatic_styles, Element): 419 for level in range(1, 11): 420 if ( 421 automatic_styles.get_style( 422 "paragraph", _toc_entry_style_name(level) 423 ) 424 is None 425 ): 426 level_style = default_toc_level_style(level) 427 automatic_styles.append(level_style) 428 429 # Auto-fill the index 430 outline_level = self.outline_level or 10 431 level_indexes: dict[int, int] = {} 432 for header in body.get_headers(): 433 level = header.get_attribute_integer("text:outline-level") or 0 434 if level is None or level > outline_level: 435 continue 436 number_str = self._header_numbering(level_indexes, level) 437 # Make the title with "1.2.3. Title" format 438 paragraph = Paragraph(f"{number_str} {header}") 439 if use_default_styles: 440 paragraph.style = _toc_entry_style_name(level) 441 index_body.append(paragraph) # type: ignore
Table of content. The "text:table-of-content" element represents a table of contents for a document. The items that can be listed in a table of contents are:
- Headings (as defined by the outline structure of the document), up to a selected level.
- Table of contents index marks.
- Paragraphs formatted with specified paragraph styles.
Implementation: Default parameters are what most people use: protected from manual modifications and not limited in title levels.
The name is mandatory and derived automatically from the title if not given. Provide one in case of a conflict with other TOCs in the same document.
The "text:table-of-content" element has the following attributes: text:name, text:protected, text:protection-key, text:protection-key-digest-algorithm, text:style-name and xml:id.
Arguments:
title -- str
name -- str
protected -- bool
outline_level -- int
style -- str
title_style -- str
entry_style -- str
219 def __init__( 220 self, 221 title: str = "Table of Contents", 222 name: str | None = None, 223 protected: bool = True, 224 outline_level: int = 0, 225 style: str | None = None, 226 title_style: str = "Contents_20_Heading", 227 entry_style: str = "Contents_20_%d", 228 **kwargs: Any, 229 ) -> None: 230 super().__init__(**kwargs) 231 if self._do_init: 232 if style: 233 self.style = style 234 if protected: 235 self.protected = protected 236 if name is None: 237 self.name = f"{title}1" 238 # Create the source template 239 toc_source = self.create_toc_source( 240 title, outline_level, title_style, entry_style 241 ) 242 self.append(toc_source) 243 # Create the index body automatically with the index title 244 if title: 245 # This style is in the template document 246 self.set_toc_title(title, text_style=title_style)
248 @staticmethod 249 def create_toc_source( 250 title: str, 251 outline_level: int, 252 title_style: str, 253 entry_style: str, 254 ) -> Element: 255 toc_source = Element.from_tag("text:table-of-content-source") 256 toc_source.set_attribute("text:outline-level", str(outline_level)) 257 if title: 258 title_template = IndexTitleTemplate() 259 if title_style: 260 # This style is in the template document 261 title_template.style = title_style 262 title_template.text = title 263 toc_source.append(title_template) 264 for level in range(1, 11): 265 template = TocEntryTemplate(outline_level=level) 266 if entry_style: 267 template.style = entry_style % level 268 toc_source.append(template) 269 return toc_source
274 def get_formatted_text(self, context: dict | None = None) -> str: 275 index_body = self.get_element("text:index-body") 276 277 if index_body is None: 278 return "" 279 if context is None: 280 context = {} 281 if context.get("rst_mode"): 282 return "\n.. contents::\n\n" 283 284 result = [] 285 for element in index_body.children: 286 if element.tag == "text:index-title": 287 for child_element in element.children: 288 result.append(child_element.get_formatted_text(context).strip()) 289 else: 290 result.append(element.get_formatted_text(context).strip()) 291 return "\n".join(result)
This function should return a beautiful version of the text.
331 def set_toc_title( 332 self, 333 title: str, 334 style: str | None = None, 335 text_style: str | None = None, 336 ) -> None: 337 index_body = self.body 338 if index_body is None: 339 self.body = None 340 index_body = self.body 341 index_title = index_body.get_element(IndexTitle._tag) # type: ignore 342 if index_title is None: 343 name = f"{self.name}_Head" 344 index_title = IndexTitle( 345 name=name, style=style, title_text=title, text_style=text_style 346 ) 347 index_body.append(index_title) # type: ignore 348 else: 349 if style: 350 index_title.style = style # type: ignore 351 paragraph = index_title.get_paragraph() 352 if paragraph is None: 353 paragraph = Paragraph() 354 index_title.append(paragraph) 355 if text_style: 356 paragraph.style = text_style # type: ignore 357 paragraph.text = title
377 def fill( # noqa: C901 378 self, 379 document: Document | None = None, 380 use_default_styles: bool = True, 381 ) -> None: 382 """Fill the TOC with the titles found in the document. A TOC is not 383 contextual so it will catch all titles before and after its insertion. 384 If the TOC is not attached to a document, attach it beforehand or 385 provide one as argument. 386 387 For having a pretty TOC, let use_default_styles by default. 388 389 Arguments: 390 391 document -- Document 392 393 use_default_styles -- bool 394 """ 395 # Find the body 396 if document is not None: 397 body: Element | None = document.body 398 else: 399 body = self.document_body 400 if body is None: 401 raise ValueError("The TOC must be related to a document somehow") 402 403 # Save the title 404 index_body = self.body 405 title = index_body.get_element("text:index-title") # type: ignore 406 407 # Clean the old index-body 408 self.body = None 409 index_body = self.body 410 411 # Restore the title 412 if title and str(title): 413 index_body.insert(title, position=0) # type: ignore 414 415 # Insert default TOC style 416 if use_default_styles: 417 automatic_styles = body.get_element("//office:automatic-styles") 418 if isinstance(automatic_styles, Element): 419 for level in range(1, 11): 420 if ( 421 automatic_styles.get_style( 422 "paragraph", _toc_entry_style_name(level) 423 ) 424 is None 425 ): 426 level_style = default_toc_level_style(level) 427 automatic_styles.append(level_style) 428 429 # Auto-fill the index 430 outline_level = self.outline_level or 10 431 level_indexes: dict[int, int] = {} 432 for header in body.get_headers(): 433 level = header.get_attribute_integer("text:outline-level") or 0 434 if level is None or level > outline_level: 435 continue 436 number_str = self._header_numbering(level_indexes, level) 437 # Make the title with "1.2.3. Title" format 438 paragraph = Paragraph(f"{number_str} {header}") 439 if use_default_styles: 440 paragraph.style = _toc_entry_style_name(level) 441 index_body.append(paragraph) # type: ignore
Fill the TOC with the titles found in the document. A TOC is not contextual so it will catch all titles before and after its insertion. If the TOC is not attached to a document, attach it beforehand or provide one as argument.
For having a pretty TOC, let use_default_styles by default.
Arguments:
document -- Document
use_default_styles -- bool
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
194class Tab(Element): 195 """This element represents the [UNICODE] tab character (HORIZONTAL 196 TABULATION, U+0009). 197 198 The position attribute contains the number of the tab-stop to which 199 a tab character refers. The position 0 marks the start margin of a 200 paragraph. Note: The position attribute is only a hint to help non-layout 201 oriented consumers to determine the tab/tab-stop association. Layout 202 oriented consumers should determine the tab positions based on the style 203 information 204 """ 205 206 _tag = "text:tab" 207 _properties: tuple[PropDef, ...] = (PropDef("position", "text:tab-ref"),) 208 209 def __init__(self, position: int | None = None, **kwargs: Any) -> None: 210 """ 211 Arguments: 212 213 position -- int 214 """ 215 super().__init__(**kwargs) 216 if self._do_init and position is not None and position >= 0: 217 self.position = str(position)
This element represents the [UNICODE] tab character (HORIZONTAL TABULATION, U+0009).
The position attribute contains the number of the tab-stop to which a tab character refers. The position 0 marks the start margin of a paragraph. Note: The position attribute is only a hint to help non-layout oriented consumers to determine the tab/tab-stop association. Layout oriented consumers should determine the tab positions based on the style information
209 def __init__(self, position: int | None = None, **kwargs: Any) -> None: 210 """ 211 Arguments: 212 213 position -- int 214 """ 215 super().__init__(**kwargs) 216 if self._do_init and position is not None and position >= 0: 217 self.position = str(position)
Arguments:
position -- int
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
94class TabStopStyle(Element): 95 """ODF "style:tab-stop" 96 Base style for a TOC entryBase style for a TOC entry 97 """ 98 99 _tag = "style:tab-stop" 100 _properties = ( 101 PropDef("style_char", "style:char"), 102 PropDef("leader_color", "style:leader-color"), 103 PropDef("leader_style", "style:leader-style"), 104 PropDef("leader_text", "style:leader-text"), 105 PropDef("leader_text_style", "style:leader-text-style"), 106 PropDef("leader_type", "style:leader-type"), 107 PropDef("leader_width", "style:leader-width"), 108 PropDef("style_position", "style:position"), 109 PropDef("style_type", "style:type"), 110 ) 111 112 def __init__( # noqa: C901 113 self, 114 style_char: str | None = None, 115 leader_color: str | None = None, 116 leader_style: str | None = None, 117 leader_text: str | None = None, 118 leader_text_style: str | None = None, 119 leader_type: str | None = None, 120 leader_width: str | None = None, 121 style_position: str | None = None, 122 style_type: str | None = None, 123 **kwargs: Any, 124 ): 125 super().__init__(**kwargs) 126 if self._do_init: 127 if style_char: 128 self.style_char = style_char 129 if leader_color: 130 self.leader_color = leader_color 131 if leader_style: 132 self.leader_style = leader_style 133 if leader_text: 134 self.leader_text = leader_text 135 if leader_text_style: 136 self.leader_text_style = leader_text_style 137 if leader_type: 138 self.leader_type = leader_type 139 if leader_width: 140 self.leader_width = leader_width 141 if style_position: 142 self.style_position = style_position 143 if style_type: 144 self.style_type = style_type
ODF "style:tab-stop" Base style for a TOC entryBase style for a TOC entry
112 def __init__( # noqa: C901 113 self, 114 style_char: str | None = None, 115 leader_color: str | None = None, 116 leader_style: str | None = None, 117 leader_text: str | None = None, 118 leader_text_style: str | None = None, 119 leader_type: str | None = None, 120 leader_width: str | None = None, 121 style_position: str | None = None, 122 style_type: str | None = None, 123 **kwargs: Any, 124 ): 125 super().__init__(**kwargs) 126 if self._do_init: 127 if style_char: 128 self.style_char = style_char 129 if leader_color: 130 self.leader_color = leader_color 131 if leader_style: 132 self.leader_style = leader_style 133 if leader_text: 134 self.leader_text = leader_text 135 if leader_text_style: 136 self.leader_text_style = leader_text_style 137 if leader_type: 138 self.leader_type = leader_type 139 if leader_width: 140 self.leader_width = leader_width 141 if style_position: 142 self.style_position = style_position 143 if style_type: 144 self.style_type = style_type
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
287class Table(Element): 288 """ODF table "table:table" """ 289 290 _tag = "table:table" 291 _caching = True 292 _append = Element.append 293 294 def __init__( 295 self, 296 name: str | None = None, 297 width: int | None = None, 298 height: int | None = None, 299 protected: bool = False, 300 protection_key: str | None = None, 301 display: bool = True, 302 printable: bool = True, 303 print_ranges: list[str] | None = None, 304 style: str | None = None, 305 **kwargs: Any, 306 ) -> None: 307 """Create a table element, optionally prefilled with "height" rows of 308 "width" cells each. 309 310 If the table is to be protected, a protection key must be provided, 311 i.e. a hash value of the password. 312 313 If the table must not be displayed, set "display" to False. 314 315 If the table must not be printed, set "printable" to False. The table 316 will not be printed when it is not displayed, whatever the value of 317 this argument. 318 319 Ranges of cells to print can be provided as a list of cell ranges, 320 e.g. ['E6:K12', 'P6:R12'] or directly as a raw string, e.g. 321 "E6:K12 P6:R12". 322 323 You can access and modify the XML tree manually, but you probably want 324 to use the API to access and alter cells. It will save you from 325 handling repetitions and the same number of cells for each row. 326 327 If you use both the table API and the XML API, you are on your own for 328 ensuiring model integrity. 329 330 Arguments: 331 332 name -- str 333 334 width -- int 335 336 height -- int 337 338 protected -- bool 339 340 protection_key -- str 341 342 display -- bool 343 344 printable -- bool 345 346 print_ranges -- list 347 348 style -- str 349 """ 350 super().__init__(**kwargs) 351 self._indexes = {} 352 self._indexes["_cmap"] = {} 353 self._indexes["_tmap"] = {} 354 if self._do_init: 355 self.name = name 356 if protected: 357 self.protected = protected 358 self.set_protection_key = protection_key 359 if not display: 360 self.displayed = display 361 if not printable: 362 self.printable = printable 363 if print_ranges: 364 self.print_ranges = print_ranges 365 if style: 366 self.style = style 367 # Prefill the table 368 if width is not None or height is not None: 369 width = width or 1 370 height = height or 1 371 # Column groups for style information 372 columns = Column(repeated=width) 373 self._append(columns) 374 for _i in range(height): 375 row = Row(width) 376 self._append(row) 377 self._compute_table_cache() 378 379 def __str__(self) -> str: 380 def write_content(csv_writer: object) -> None: 381 for values in self.iter_values(): 382 line = [] 383 for value in values: 384 if value is None: 385 value = "" 386 if isinstance(value, str): 387 value = value.strip() 388 line.append(value) 389 csv_writer.writerow(line) # type: ignore 390 391 out = StringIO(newline=os.linesep) 392 csv_writer = csv.writer( 393 out, 394 delimiter=" ", 395 doublequote=False, 396 escapechar="\\", 397 lineterminator=os.linesep, 398 quotechar='"', 399 quoting=csv.QUOTE_NONNUMERIC, 400 ) 401 write_content(csv_writer) 402 return out.getvalue() 403 404 def _translate_y_from_any(self, y: str | int) -> int: 405 # "3" (couting from 1) -> 2 (couting from 0) 406 return translate_from_any(y, self.height, 1) 407 408 def _translate_table_coordinates_list( 409 self, 410 coord: tuple | list, 411 ) -> tuple[int | None, ...]: 412 height = self.height 413 width = self.width 414 # assuming we got int values 415 if len(coord) == 1: 416 # It is a row 417 y = coord[0] 418 if y and y < 0: 419 y = increment(y, height) 420 return (None, y, None, y) 421 if len(coord) == 2: 422 # It is a row range, not a cell, because context is table 423 y = coord[0] 424 if y and y < 0: 425 y = increment(y, height) 426 t = coord[1] 427 if t and t < 0: 428 t = increment(t, height) 429 return (None, y, None, t) 430 # should be 4 int 431 x, y, z, t = coord 432 if x and x < 0: 433 x = increment(x, width) 434 if y and y < 0: 435 y = increment(y, height) 436 if z and z < 0: 437 z = increment(z, width) 438 if t and t < 0: 439 t = increment(t, height) 440 return (x, y, z, t) 441 442 def _translate_table_coordinates_str( 443 self, 444 coord_str: str, 445 ) -> tuple[int | None, ...]: 446 height = self.height 447 width = self.width 448 coord = convert_coordinates(coord_str) 449 if len(coord) == 2: 450 x, y = coord 451 if x and x < 0: 452 x = increment(x, width) 453 if y and y < 0: 454 y = increment(y, height) 455 # extent to an area : 456 return (x, y, x, y) 457 x, y, z, t = coord 458 if x and x < 0: 459 x = increment(x, width) 460 if y and y < 0: 461 y = increment(y, height) 462 if z and z < 0: 463 z = increment(z, width) 464 if t and t < 0: 465 t = increment(t, height) 466 return (x, y, z, t) 467 468 def _translate_table_coordinates( 469 self, 470 coord: tuple | list | str, 471 ) -> tuple[int | None, ...]: 472 if isinstance(coord, str): 473 return self._translate_table_coordinates_str(coord) 474 return self._translate_table_coordinates_list(coord) 475 476 def _translate_column_coordinates_str( 477 self, 478 coord_str: str, 479 ) -> tuple[int | None, ...]: 480 width = self.width 481 height = self.height 482 coord = convert_coordinates(coord_str) 483 if len(coord) == 2: 484 x, y = coord 485 if x and x < 0: 486 x = increment(x, width) 487 if y and y < 0: 488 y = increment(y, height) 489 # extent to an area : 490 return (x, y, x, y) 491 x, y, z, t = coord 492 if x and x < 0: 493 x = increment(x, width) 494 if y and y < 0: 495 y = increment(y, height) 496 if z and z < 0: 497 z = increment(z, width) 498 if t and t < 0: 499 t = increment(t, height) 500 return (x, y, z, t) 501 502 def _translate_column_coordinates_list( 503 self, 504 coord: tuple | list, 505 ) -> tuple[int | None, ...]: 506 width = self.width 507 height = self.height 508 # assuming we got int values 509 if len(coord) == 1: 510 # It is a column 511 x = coord[0] 512 if x and x < 0: 513 x = increment(x, width) 514 return (x, None, x, None) 515 if len(coord) == 2: 516 # It is a column range, not a cell, because context is table 517 x = coord[0] 518 if x and x < 0: 519 x = increment(x, width) 520 z = coord[1] 521 if z and z < 0: 522 z = increment(z, width) 523 return (x, None, z, None) 524 # should be 4 int 525 x, y, z, t = coord 526 if x and x < 0: 527 x = increment(x, width) 528 if y and y < 0: 529 y = increment(y, height) 530 if z and z < 0: 531 z = increment(z, width) 532 if t and t < 0: 533 t = increment(t, height) 534 return (x, y, z, t) 535 536 def _translate_column_coordinates( 537 self, 538 coord: tuple | list | str, 539 ) -> tuple[int | None, ...]: 540 if isinstance(coord, str): 541 return self._translate_column_coordinates_str(coord) 542 return self._translate_column_coordinates_list(coord) 543 544 def _translate_cell_coordinates( 545 self, 546 coord: tuple | list | str, 547 ) -> tuple[int | None, int | None]: 548 # we want an x,y result 549 coord = convert_coordinates(coord) 550 if len(coord) == 2: 551 x, y = coord 552 # If we got an area, take the first cell 553 elif len(coord) == 4: 554 x, y, z, t = coord 555 else: 556 raise ValueError(str(coord)) 557 if x and x < 0: 558 x = increment(x, self.width) 559 if y and y < 0: 560 y = increment(y, self.height) 561 return (x, y) 562 563 def _compute_table_cache(self) -> None: 564 idx_repeated_seq = self.elements_repeated_sequence( 565 _xpath_row, "table:number-rows-repeated" 566 ) 567 self._tmap = make_cache_map(idx_repeated_seq) 568 idx_repeated_seq = self.elements_repeated_sequence( 569 _xpath_column, "table:number-columns-repeated" 570 ) 571 self._cmap = make_cache_map(idx_repeated_seq) 572 573 def _update_width(self, row: Row) -> None: 574 """Synchronize the number of columns if the row is bigger. 575 576 Append, don't insert, not to disturb the current layout. 577 """ 578 diff = row.width - self.width 579 if diff > 0: 580 self.append_column(Column(repeated=diff)) 581 582 def _get_formatted_text_normal(self, context: dict | None) -> str: 583 result = [] 584 for row in self.traverse(): 585 for cell in row.traverse(): 586 value = cell.get_value(try_get_text=False) 587 # None ? 588 if value is None: 589 # Try with get_formatted_text on the elements 590 value = [] 591 for element in cell.children: 592 value.append(element.get_formatted_text(context)) 593 value = "".join(value) 594 else: 595 value = str(value) 596 result.append(value) 597 result.append("\n") 598 result.append("\n") 599 return "".join(result) 600 601 def _get_formatted_text_rst(self, context: dict) -> str: # noqa: C901 602 context["no_img_level"] += 1 603 # Strip the table => We must clone 604 table = self.clone 605 table.rstrip(aggressive=True) # type: ignore 606 607 # Fill the rows 608 rows = [] 609 cols_nb = 0 610 cols_size: dict[int, int] = {} 611 for odf_row in table.traverse(): # type: ignore 612 row = [] 613 for i, cell in enumerate(odf_row.traverse()): 614 value = cell.get_value(try_get_text=False) 615 # None ? 616 if value is None: 617 # Try with get_formatted_text on the elements 618 value = [] 619 for element in cell.children: 620 value.append(element.get_formatted_text(context)) 621 value = "".join(value) 622 else: 623 value = str(value) 624 value = value.strip() 625 # Strip the empty columns 626 if value: 627 cols_nb = max(cols_nb, i + 1) 628 # Compute the size of each columns (at least 2) 629 cols_size[i] = max(cols_size.get(i, 2), len(value)) 630 # Append 631 row.append(value) 632 rows.append(row) 633 634 # Nothing ? 635 if cols_nb == 0: 636 return "" 637 638 # Prevent a crash with empty columns (by example with images) 639 for col, size in cols_size.items(): 640 if size == 0: 641 cols_size[col] = 1 642 643 # Update cols_size 644 LINE_MAX = 100 645 COL_MIN = 16 646 647 free_size = LINE_MAX - (cols_nb - 1) * 3 - 4 648 real_size = sum([cols_size[i] for i in range(cols_nb)]) 649 if real_size > free_size: 650 factor = float(free_size) / real_size 651 652 for i in range(cols_nb): 653 old_size = cols_size[i] 654 655 # The cell is already small 656 if old_size <= COL_MIN: 657 continue 658 659 new_size = int(factor * old_size) 660 661 if new_size < COL_MIN: 662 new_size = COL_MIN 663 cols_size[i] = new_size 664 665 # Convert ! 666 result: list[str] = [""] 667 # Construct the first/last line 668 line: list[str] = [] 669 for i in range(cols_nb): 670 line.append("=" * cols_size[i]) 671 line.append(" ") 672 line_str = "".join(line) 673 674 # Add the lines 675 result.append(line_str) 676 for row in rows: 677 # Wrap the row 678 wrapped_row = [] 679 for i, value in enumerate(row[:cols_nb]): 680 wrapped_value = [] 681 for part in value.split("\n"): 682 # Hack to handle correctly the lists or the directives 683 subsequent_indent = "" 684 part_lstripped = part.lstrip() 685 if part_lstripped.startswith("-") or part_lstripped.startswith( 686 ".." 687 ): 688 subsequent_indent = " " * (len(part) - len(part.lstrip()) + 2) 689 wrapped_part = wrap( 690 part, width=cols_size[i], subsequent_indent=subsequent_indent 691 ) 692 if wrapped_part: 693 wrapped_value.extend(wrapped_part) 694 else: 695 wrapped_value.append("") 696 wrapped_row.append(wrapped_value) 697 698 # Append! 699 for j in range(max([1] + [len(values) for values in wrapped_row])): 700 txt_row: list[str] = [] 701 for i in range(cols_nb): 702 values = wrapped_row[i] if i < len(wrapped_row) else [] 703 704 # An empty cell ? 705 if len(values) - 1 < j or not values[j]: 706 if i == 0 and j == 0: 707 txt_row.append("..") 708 txt_row.append(" " * (cols_size[i] - 1)) 709 else: 710 txt_row.append(" " * (cols_size[i] + 1)) 711 continue 712 713 # Not empty 714 value = values[j] 715 txt_row.append(value) 716 txt_row.append(" " * (cols_size[i] - len(value) + 1)) 717 result.append("".join(txt_row)) 718 719 result.append(line_str) 720 result.append("") 721 result.append("") 722 result_str = "\n".join(result) 723 724 context["no_img_level"] -= 1 725 return result_str 726 727 def _translate_x_from_any(self, x: str | int) -> int: 728 return translate_from_any(x, self.width, 0) 729 730 # 731 # Public API 732 # 733 734 def append(self, something: Element | str) -> None: 735 """Dispatch .append() call to append_row() or append_column().""" 736 if isinstance(something, Row): 737 self.append_row(something) 738 elif isinstance(something, Column): 739 self.append_column(something) 740 else: 741 # probably still an error 742 self._append(something) 743 744 @property 745 def height(self) -> int: 746 """Get the current height of the table. 747 748 Return: int 749 """ 750 try: 751 height = self._tmap[-1] + 1 752 except Exception: 753 height = 0 754 return height 755 756 @property 757 def width(self) -> int: 758 """Get the current width of the table, measured on columns. 759 760 Rows may have different widths, use the Table API to ensure width 761 consistency. 762 763 Return: int 764 """ 765 # Columns are our reference for user expected width 766 767 try: 768 width = self._cmap[-1] + 1 769 except Exception: 770 width = 0 771 772 # columns = self._get_columns() 773 # repeated = self.xpath( 774 # 'table:table-column/@table:number-columns-repeated') 775 # unrepeated = len(columns) - len(repeated) 776 # ws = sum(int(r) for r in repeated) + unrepeated 777 # if w != ws: 778 # print "WARNING ws", ws, "w", w 779 780 return width 781 782 @property 783 def size(self) -> tuple[int, int]: 784 """Shortcut to get the current width and height of the table. 785 786 Return: (int, int) 787 """ 788 return self.width, self.height 789 790 @property 791 def name(self) -> str | None: 792 """Get / set the name of the table.""" 793 return self.get_attribute_string("table:name") 794 795 @name.setter 796 def name(self, name: str) -> None: 797 name = _table_name_check(name) 798 # first, update named ranges 799 # fixme : delete name ranges when deleting table, too. 800 for named_range in self.get_named_ranges(table_name=self.name): 801 named_range.set_table_name(name) 802 self.set_attribute("table:name", name) 803 804 @property 805 def protected(self) -> bool: 806 return bool(self.get_attribute("table:protected")) 807 808 @protected.setter 809 def protected(self, protect: bool) -> None: 810 self.set_attribute("table:protected", protect) 811 812 @property 813 def protection_key(self) -> str | None: 814 return self.get_attribute_string("table:protection-key") 815 816 @protection_key.setter 817 def protection_key(self, key: str) -> None: 818 self.set_attribute("table:protection-key", key) 819 820 @property 821 def displayed(self) -> bool: 822 return bool(self.get_attribute("table:display")) 823 824 @displayed.setter 825 def displayed(self, display: bool) -> None: 826 self.set_attribute("table:display", display) 827 828 @property 829 def printable(self) -> bool: 830 printable = self.get_attribute("table:print") 831 # Default value 832 if printable is None: 833 return True 834 return bool(printable) 835 836 @printable.setter 837 def printable(self, printable: bool) -> None: 838 self.set_attribute("table:print", printable) 839 840 @property 841 def print_ranges(self) -> list[str]: 842 ranges = self.get_attribute_string("table:print-ranges") 843 if isinstance(ranges, str): 844 return ranges.split() 845 return [] 846 847 @print_ranges.setter 848 def print_ranges(self, ranges: list[str] | None) -> None: 849 if isinstance(ranges, (list, tuple)): 850 self.set_attribute("table:print-ranges", " ".join(ranges)) 851 else: 852 self.set_attribute("table:print-ranges", ranges) 853 854 @property 855 def style(self) -> str | None: 856 """Get / set the style of the table 857 858 Return: str 859 """ 860 return self.get_attribute_string("table:style-name") 861 862 @style.setter 863 def style(self, style: str | Element) -> None: 864 self.set_style_attribute("table:style-name", style) 865 866 def get_formatted_text(self, context: dict | None = None) -> str: 867 if context and context["rst_mode"]: 868 return self._get_formatted_text_rst(context) 869 return self._get_formatted_text_normal(context) 870 871 def get_values( 872 self, 873 coord: tuple | list | str | None = None, 874 cell_type: str | None = None, 875 complete: bool = True, 876 get_type: bool = False, 877 flat: bool = False, 878 ) -> list: 879 """Get a matrix of values of the table. 880 881 Filter by coordinates will parse the area defined by the coordinates. 882 883 If 'cell_type' is used and 'complete' is True (default), missing values 884 are replaced by None. 885 Filter by ' cell_type = "all" ' will retrieve cells of any 886 type, aka non empty cells. 887 888 If 'cell_type' is None, complete is always True : with no cell type 889 queried, get_values() returns None for each empty cell, the length 890 each lists is equal to the width of the table. 891 892 If get_type is True, returns tuples (value, ODF type of value), or 893 (None, None) for empty cells with complete True. 894 895 If flat is True, the methods return a single list of all the values. 896 By default, flat is False. 897 898 Arguments: 899 900 coord -- str or tuple of int : coordinates of area 901 902 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 903 'currency', 'percentage' or 'all' 904 905 complete -- boolean 906 907 get_type -- boolean 908 909 Return: list of lists of Python types 910 """ 911 if coord: 912 x, y, z, t = self._translate_table_coordinates(coord) 913 else: 914 x = y = z = t = None 915 data = [] 916 for row in self.traverse(start=y, end=t): 917 if z is None: 918 width = self.width 919 else: 920 width = min(z + 1, self.width) 921 if x is not None: 922 width -= x 923 values = row.get_values( 924 (x, z), 925 cell_type=cell_type, 926 complete=complete, 927 get_type=get_type, 928 ) 929 # complete row to match request width 930 if complete: 931 if get_type: 932 values.extend([(None, None)] * (width - len(values))) 933 else: 934 values.extend([None] * (width - len(values))) 935 if flat: 936 data.extend(values) 937 else: 938 data.append(values) 939 return data 940 941 def iter_values( 942 self, 943 coord: tuple | list | str | None = None, 944 cell_type: str | None = None, 945 complete: bool = True, 946 get_type: bool = False, 947 ) -> Iterator[list]: 948 """Iterate through lines of Python values of the table. 949 950 Filter by coordinates will parse the area defined by the coordinates. 951 952 cell_type, complete, grt_type : see get_values() 953 954 955 956 Arguments: 957 958 coord -- str or tuple of int : coordinates of area 959 960 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 961 'currency', 'percentage' or 'all' 962 963 complete -- boolean 964 965 get_type -- boolean 966 967 Return: iterator of lists 968 """ 969 if coord: 970 x, y, z, t = self._translate_table_coordinates(coord) 971 else: 972 x = y = z = t = None 973 for row in self.traverse(start=y, end=t): 974 if z is None: 975 width = self.width 976 else: 977 width = min(z + 1, self.width) 978 if x is not None: 979 width -= x 980 values = row.get_values( 981 (x, z), 982 cell_type=cell_type, 983 complete=complete, 984 get_type=get_type, 985 ) 986 # complete row to match column width 987 if complete: 988 if get_type: 989 values.extend([(None, None)] * (width - len(values))) 990 else: 991 values.extend([None] * (width - len(values))) 992 yield values 993 994 def set_values( 995 self, 996 values: list, 997 coord: tuple | list | str | None = None, 998 style: str | None = None, 999 cell_type: str | None = None, 1000 currency: str | None = None, 1001 ) -> None: 1002 """Set the value of cells in the table, from the 'coord' position 1003 with values. 1004 1005 'coord' is the coordinate of the upper left cell to be modified by 1006 values. If 'coord' is None, default to the position (0,0) ("A1"). 1007 If 'coord' is an area (e.g. "A2:B5"), the upper left position of this 1008 area is used as coordinate. 1009 1010 The table is *not* cleared before the operation, to reset the table 1011 before setting values, use table.clear(). 1012 1013 A list of lists is expected, with as many lists as rows, and as many 1014 items in each sublist as cells to be setted. None values in the list 1015 will create empty cells with no cell type (but eventually a style). 1016 1017 Arguments: 1018 1019 coord -- tuple or str 1020 1021 values -- list of lists of python types 1022 1023 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 1024 'string' or 'time' 1025 1026 currency -- three-letter str 1027 1028 style -- str 1029 """ 1030 if coord: 1031 x, y = self._translate_cell_coordinates(coord) 1032 else: 1033 x = y = 0 1034 if y is None: 1035 y = 0 1036 if x is None: 1037 x = 0 1038 y -= 1 1039 for row_values in values: 1040 y += 1 1041 if not row_values: 1042 continue 1043 row = self.get_row(y, clone=True) 1044 repeated = row.repeated or 1 1045 if repeated >= 2: 1046 row.repeated = None 1047 row.set_values( 1048 row_values, 1049 start=x, 1050 cell_type=cell_type, 1051 currency=currency, 1052 style=style, 1053 ) 1054 self.set_row(y, row, clone=False) 1055 self._update_width(row) 1056 1057 def rstrip(self, aggressive: bool = False) -> None: 1058 """Remove *in-place* empty rows below and empty cells at the right of 1059 the table. Cells are empty if they contain no value or it evaluates 1060 to False, and no style. 1061 1062 If aggressive is True, empty cells with style are removed too. 1063 1064 Argument: 1065 1066 aggressive -- bool 1067 """ 1068 # Step 1: remove empty rows below the table 1069 for row in reversed(self._get_rows()): 1070 if row.is_empty(aggressive=aggressive): 1071 row.parent.delete(row) # type: ignore 1072 else: 1073 break 1074 # Step 2: rstrip remaining rows 1075 max_width = 0 1076 for row in self._get_rows(): 1077 row.rstrip(aggressive=aggressive) 1078 # keep count of the biggest row 1079 max_width = max(max_width, row.width) 1080 # raz cache of rows 1081 self._indexes["_tmap"] = {} 1082 # Step 3: trim columns to match max_width 1083 columns = self._get_columns() 1084 repeated_cols = self.xpath("table:table-column/@table:number-columns-repeated") 1085 if not isinstance(repeated_cols, list): 1086 raise TypeError 1087 unrepeated = len(columns) - len(repeated_cols) 1088 column_width = sum(int(r) for r in repeated_cols) + unrepeated # type: ignore 1089 diff = column_width - max_width 1090 if diff > 0: 1091 for column in reversed(columns): 1092 repeated = column.repeated or 1 1093 repeated = repeated - diff 1094 if repeated > 0: 1095 column.repeated = repeated 1096 break 1097 else: 1098 column.parent.delete(column) 1099 diff = -repeated 1100 if diff == 0: 1101 break 1102 # raz cache of columns 1103 self._indexes["_cmap"] = {} 1104 self._compute_table_cache() 1105 1106 def optimize_width(self) -> None: 1107 """Remove *in-place* empty rows below and empty cells at the right of 1108 the table. Keep repeated styles of empty cells but minimize row width. 1109 """ 1110 self._optimize_width_trim_rows() 1111 width = self._optimize_width_length() 1112 self._optimize_width_rstrip_rows(width) 1113 self._optimize_width_adapt_columns(width) 1114 1115 def _optimize_width_trim_rows(self) -> None: 1116 count = -1 # to keep one empty row 1117 for row in reversed(self._get_rows()): 1118 if row.is_empty(aggressive=False): 1119 count += 1 1120 else: 1121 break 1122 if count > 0: 1123 for row in reversed(self._get_rows()): 1124 row.parent.delete(row) # type: ignore 1125 count -= 1 1126 if count <= 0: 1127 break 1128 try: 1129 last_row = self._get_rows()[-1] 1130 last_row._set_repeated(None) 1131 except IndexError: 1132 pass 1133 # raz cache of rows 1134 self._indexes["_tmap"] = {} 1135 1136 def _optimize_width_length(self) -> int: 1137 return max(row.minimized_width() for row in self._get_rows()) 1138 1139 def _optimize_width_rstrip_rows(self, width: int) -> None: 1140 for row in self._get_rows(): 1141 row.force_width(width) 1142 1143 def _optimize_width_adapt_columns(self, width: int) -> None: 1144 # trim columns to match minimal_width 1145 columns = self._get_columns() 1146 repeated_cols = self.xpath("table:table-column/@table:number-columns-repeated") 1147 if not isinstance(repeated_cols, list): 1148 raise TypeError 1149 unrepeated = len(columns) - len(repeated_cols) 1150 column_width = sum(int(r) for r in repeated_cols) + unrepeated # type: ignore 1151 diff = column_width - width 1152 if diff > 0: 1153 for column in reversed(columns): 1154 repeated = column.repeated or 1 1155 repeated = repeated - diff 1156 if repeated > 0: 1157 column.repeated = repeated 1158 break 1159 else: 1160 column.parent.delete(column) 1161 diff = -repeated 1162 if diff == 0: 1163 break 1164 # raz cache of columns 1165 self._indexes["_cmap"] = {} 1166 self._compute_table_cache() 1167 1168 def transpose(self, coord: tuple | list | str | None = None) -> None: # noqa: C901 1169 """Swap *in-place* rows and columns of the table. 1170 1171 If 'coord' is not None, apply transpose only to the area defined by the 1172 coordinates. Beware, if area is not square, some cells mays be over 1173 written during the process. 1174 1175 Arguments: 1176 1177 coord -- str or tuple of int : coordinates of area 1178 1179 start -- int or str 1180 """ 1181 data = [] 1182 if coord is None: 1183 for row in self.traverse(): 1184 data.append(list(row.traverse())) 1185 transposed_data = zip_longest(*data) 1186 self.clear() 1187 # new_rows = [] 1188 for row_cells in transposed_data: 1189 if not isiterable(row_cells): 1190 row_cells = (row_cells,) 1191 row = Row() 1192 row.extend_cells(row_cells) 1193 self.append_row(row, clone=False) 1194 self._compute_table_cache() 1195 else: 1196 x, y, z, t = self._translate_table_coordinates(coord) 1197 if x is None: 1198 x = 0 1199 else: 1200 x = min(x, self.width - 1) 1201 if z is None: 1202 z = self.width - 1 1203 else: 1204 z = min(z, self.width - 1) 1205 if y is None: 1206 y = 0 1207 else: 1208 y = min(y, self.height - 1) 1209 if t is None: 1210 t = self.height - 1 1211 else: 1212 t = min(t, self.height - 1) 1213 for row in self.traverse(start=y, end=t): 1214 data.append(list(row.traverse(start=x, end=z))) 1215 transposed_data = zip_longest(*data) 1216 # clear locally 1217 w = z - x + 1 1218 h = t - y + 1 1219 if w != h: 1220 nones = [[None] * w for i in range(h)] 1221 self.set_values(nones, coord=(x, y, z, t)) 1222 # put transposed 1223 filtered_data: list[tuple[Cell]] = [] 1224 for row_cells in transposed_data: 1225 if isinstance(row_cells, (list, tuple)): 1226 filtered_data.append(row_cells) 1227 else: 1228 filtered_data.append((row_cells,)) 1229 self.set_cells(filtered_data, (x, y, x + h - 1, y + w - 1)) 1230 self._compute_table_cache() 1231 1232 def is_empty(self, aggressive: bool = False) -> bool: 1233 """Return whether every cell in the table has no value or the value 1234 evaluates to False (empty string), and no style. 1235 1236 If aggressive is True, empty cells with style are considered empty. 1237 1238 Arguments: 1239 1240 aggressive -- bool 1241 """ 1242 return all(row.is_empty(aggressive=aggressive) for row in self._get_rows()) 1243 1244 # 1245 # Rows 1246 # 1247 1248 def _get_rows(self) -> list[Row]: 1249 return self.get_elements(_xpath_row) # type: ignore 1250 1251 def traverse( # noqa: C901 1252 self, 1253 start: int | None = None, 1254 end: int | None = None, 1255 ) -> Iterator[Row]: 1256 """Yield as many row elements as expected rows in the table, i.e. 1257 expand repetitions by returning the same row as many times as 1258 necessary. 1259 1260 Arguments: 1261 1262 start -- int 1263 1264 end -- int 1265 1266 Copies are returned, use set_row() to push them back. 1267 """ 1268 idx = -1 1269 before = -1 1270 y = 0 1271 if start is None and end is None: 1272 for juska in self._tmap: 1273 idx += 1 1274 if idx in self._indexes["_tmap"]: 1275 row = self._indexes["_tmap"][idx] 1276 else: 1277 row = self._get_element_idx2(_xpath_row_idx, idx) 1278 self._indexes["_tmap"][idx] = row 1279 repeated = juska - before 1280 before = juska 1281 for _i in range(repeated or 1): 1282 # Return a copy without the now obsolete repetition 1283 row = row.clone 1284 row.y = y 1285 y += 1 1286 if repeated > 1: 1287 row.repeated = None 1288 yield row 1289 else: 1290 if start is None: 1291 start = 0 1292 start = max(0, start) 1293 if end is None: 1294 try: 1295 end = self._tmap[-1] 1296 except Exception: 1297 end = -1 1298 start_map = find_odf_idx(self._tmap, start) 1299 if start_map is None: 1300 return 1301 if start_map > 0: 1302 before = self._tmap[start_map - 1] 1303 idx = start_map - 1 1304 before = start - 1 1305 y = start 1306 for juska in self._tmap[start_map:]: 1307 idx += 1 1308 if idx in self._indexes["_tmap"]: 1309 row = self._indexes["_tmap"][idx] 1310 else: 1311 row = self._get_element_idx2(_xpath_row_idx, idx) 1312 self._indexes["_tmap"][idx] = row 1313 repeated = juska - before 1314 before = juska 1315 for _i in range(repeated or 1): 1316 if y <= end: 1317 row = row.clone 1318 row.y = y 1319 y += 1 1320 if repeated > 1 or (y == start and start > 0): 1321 row.repeated = None 1322 yield row 1323 1324 def get_rows( 1325 self, 1326 coord: tuple | list | str | None = None, 1327 style: str | None = None, 1328 content: str | None = None, 1329 ) -> list[Row]: 1330 """Get the list of rows matching the criteria. 1331 1332 Filter by coordinates will parse the area defined by the coordinates. 1333 1334 Arguments: 1335 1336 coord -- str or tuple of int : coordinates of rows 1337 1338 content -- str regex 1339 1340 style -- str 1341 1342 Return: list of rows 1343 """ 1344 if coord: 1345 _x, y, _z, t = self._translate_table_coordinates(coord) 1346 else: 1347 y = t = None 1348 # fixme : not clones ? 1349 if not content and not style: 1350 return list(self.traverse(start=y, end=t)) 1351 rows = [] 1352 for row in self.traverse(start=y, end=t): 1353 if content and not row.match(content): 1354 continue 1355 if style and style != row.style: 1356 continue 1357 rows.append(row) 1358 return rows 1359 1360 def _get_row2(self, y: int, clone: bool = True, create: bool = True) -> Row: 1361 if y >= self.height: 1362 if create: 1363 return Row() 1364 raise ValueError("Row not found") 1365 row = self._get_row2_base(y) 1366 if row is None: 1367 raise ValueError("Row not found") 1368 if clone: 1369 return row.clone 1370 return row 1371 1372 def _get_row2_base(self, y: int) -> Row | None: 1373 idx = find_odf_idx(self._tmap, y) 1374 if idx is not None: 1375 if idx in self._indexes["_tmap"]: 1376 row = self._indexes["_tmap"][idx] 1377 else: 1378 row = self._get_element_idx2(_xpath_row_idx, idx) 1379 self._indexes["_tmap"][idx] = row 1380 return row 1381 return None 1382 1383 def get_row(self, y: int | str, clone: bool = True, create: bool = True) -> Row: 1384 """Get the row at the given "y" position. 1385 1386 Position start at 0. So cell A4 is on row 3. 1387 1388 A copy is returned, use set_cell() to push it back. 1389 1390 Arguments: 1391 1392 y -- int or str 1393 1394 Return: Row 1395 """ 1396 # fixme : keep repeat ? maybe an option to functions : "raw=False" 1397 y = self._translate_y_from_any(y) 1398 row = self._get_row2(y, clone=clone, create=create) 1399 if row is None: 1400 raise ValueError("Row not found") 1401 row.y = y 1402 return row 1403 1404 def set_row(self, y: int | str, row: Row | None = None, clone: bool = True) -> Row: 1405 """Replace the row at the given position with the new one. Repetions of 1406 the old row will be adjusted. 1407 1408 If row is None, a new empty row is created. 1409 1410 Position start at 0. So cell A4 is on row 3. 1411 1412 Arguments: 1413 1414 y -- int or str 1415 1416 row -- Row 1417 1418 returns the row, with updated row.y 1419 """ 1420 if row is None: 1421 row = Row() 1422 repeated = 1 1423 clone = False 1424 else: 1425 repeated = row.repeated or 1 1426 y = self._translate_y_from_any(y) 1427 row.y = y 1428 # Outside the defined table ? 1429 diff = y - self.height 1430 if diff == 0: 1431 row_back = self.append_row(row, _repeated=repeated, clone=clone) 1432 elif diff > 0: 1433 self.append_row(Row(repeated=diff), _repeated=diff, clone=clone) 1434 row_back = self.append_row(row, _repeated=repeated, clone=clone) 1435 else: 1436 # Inside the defined table 1437 row_back = set_item_in_vault( # type: ignore 1438 y, row, self, _xpath_row_idx, "_tmap", clone=clone 1439 ) 1440 # print self.serialize(True) 1441 # Update width if necessary 1442 self._update_width(row_back) 1443 return row_back 1444 1445 def insert_row( 1446 self, y: str | int, row: Row | None = None, clone: bool = True 1447 ) -> Row: 1448 """Insert the row before the given "y" position. If no row is given, 1449 an empty one is created. 1450 1451 Position start at 0. So cell A4 is on row 3. 1452 1453 If row is None, a new empty row is created. 1454 1455 Arguments: 1456 1457 y -- int or str 1458 1459 row -- Row 1460 1461 returns the row, with updated row.y 1462 """ 1463 if row is None: 1464 row = Row() 1465 clone = False 1466 y = self._translate_y_from_any(y) 1467 diff = y - self.height 1468 if diff < 0: 1469 row_back = insert_item_in_vault(y, row, self, _xpath_row_idx, "_tmap") 1470 elif diff == 0: 1471 row_back = self.append_row(row, clone=clone) 1472 else: 1473 self.append_row(Row(repeated=diff), _repeated=diff, clone=False) 1474 row_back = self.append_row(row, clone=clone) 1475 row_back.y = y # type: ignore 1476 # Update width if necessary 1477 self._update_width(row_back) # type: ignore 1478 return row_back # type: ignore 1479 1480 def extend_rows(self, rows: list[Row] | None = None) -> None: 1481 """Append a list of rows at the end of the table. 1482 1483 Arguments: 1484 1485 rows -- list of Row 1486 """ 1487 if rows is None: 1488 rows = [] 1489 self.extend(rows) 1490 self._compute_table_cache() 1491 # Update width if necessary 1492 width = self.width 1493 for row in self.traverse(): 1494 if row.width > width: 1495 width = row.width 1496 diff = width - self.width 1497 if diff > 0: 1498 self.append_column(Column(repeated=diff)) 1499 1500 def append_row( 1501 self, 1502 row: Row | None = None, 1503 clone: bool = True, 1504 _repeated: int | None = None, 1505 ) -> Row: 1506 """Append the row at the end of the table. If no row is given, an 1507 empty one is created. 1508 1509 Position start at 0. So cell A4 is on row 3. 1510 1511 Note the columns are automatically created when the first row is 1512 inserted in an empty table. So better insert a filled row. 1513 1514 Arguments: 1515 1516 row -- Row 1517 1518 _repeated -- (optional), repeated value of the row 1519 1520 returns the row, with updated row.y 1521 """ 1522 if row is None: 1523 row = Row() 1524 _repeated = 1 1525 elif clone: 1526 row = row.clone 1527 # Appending a repeated row accepted 1528 # Do not insert next to the last row because it could be in a group 1529 self._append(row) 1530 if _repeated is None: 1531 _repeated = row.repeated or 1 1532 self._tmap = insert_map_once(self._tmap, len(self._tmap), _repeated) 1533 row.y = self.height - 1 1534 # Initialize columns 1535 if not self._get_columns(): 1536 repeated = row.width 1537 self.insert(Column(repeated=repeated), position=0) 1538 self._compute_table_cache() 1539 # Update width if necessary 1540 self._update_width(row) 1541 return row 1542 1543 def delete_row(self, y: int | str) -> None: 1544 """Delete the row at the given "y" position. 1545 1546 Position start at 0. So cell A4 is on row 3. 1547 1548 Arguments: 1549 1550 y -- int or str 1551 """ 1552 y = self._translate_y_from_any(y) 1553 # Outside the defined table 1554 if y >= self.height: 1555 return 1556 # Inside the defined table 1557 delete_item_in_vault(y, self, _xpath_row_idx, "_tmap") 1558 1559 def get_row_values( 1560 self, 1561 y: int | str, 1562 cell_type: str | None = None, 1563 complete: bool = True, 1564 get_type: bool = False, 1565 ) -> list: 1566 """Shortcut to get the list of Python values for the cells of the row 1567 at the given "y" position. 1568 1569 Position start at 0. So cell A4 is on row 3. 1570 1571 Filter by cell_type, with cell_type 'all' will retrieve cells of any 1572 type, aka non empty cells. 1573 If cell_type and complete is True, replace missing values by None. 1574 1575 If get_type is True, returns a tuple (value, ODF type of value) 1576 1577 Arguments: 1578 1579 y -- int, str 1580 1581 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 1582 'currency', 'percentage' or 'all' 1583 1584 complete -- boolean 1585 1586 get_type -- boolean 1587 1588 Return: list of lists of Python types 1589 """ 1590 values = self.get_row(y, clone=False).get_values( 1591 cell_type=cell_type, complete=complete, get_type=get_type 1592 ) 1593 # complete row to match column width 1594 if complete: 1595 if get_type: 1596 values.extend([(None, None)] * (self.width - len(values))) 1597 else: 1598 values.extend([None] * (self.width - len(values))) 1599 return values 1600 1601 def set_row_values( 1602 self, 1603 y: int | str, 1604 values: list, 1605 cell_type: str | None = None, 1606 currency: str | None = None, 1607 style: str | None = None, 1608 ) -> Row: 1609 """Shortcut to set the values of *all* cells of the row at the given 1610 "y" position. 1611 1612 Position start at 0. So cell A4 is on row 3. 1613 1614 Arguments: 1615 1616 y -- int or str 1617 1618 values -- list of Python types 1619 1620 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 1621 'string' or 'time' 1622 1623 currency -- three-letter str 1624 1625 style -- str 1626 1627 returns the row, with updated row.y 1628 """ 1629 row = Row() # needed if clones rows 1630 row.set_values(values, style=style, cell_type=cell_type, currency=currency) 1631 return self.set_row(y, row) # needed if clones rows 1632 1633 def set_row_cells(self, y: int | str, cells: list | None = None) -> Row: 1634 """Shortcut to set *all* the cells of the row at the given 1635 "y" position. 1636 1637 Position start at 0. So cell A4 is on row 3. 1638 1639 Arguments: 1640 1641 y -- int or str 1642 1643 cells -- list of Python types 1644 1645 style -- str 1646 1647 returns the row, with updated row.y 1648 """ 1649 if cells is None: 1650 cells = [] 1651 row = Row() # needed if clones rows 1652 row.extend_cells(cells) 1653 return self.set_row(y, row) # needed if clones rows 1654 1655 def is_row_empty(self, y: int | str, aggressive: bool = False) -> bool: 1656 """Return wether every cell in the row at the given "y" position has 1657 no value or the value evaluates to False (empty string), and no style. 1658 1659 Position start at 0. So cell A4 is on row 3. 1660 1661 If aggressive is True, empty cells with style are considered empty. 1662 1663 Arguments: 1664 1665 y -- int or str 1666 1667 aggressive -- bool 1668 """ 1669 return self.get_row(y, clone=False).is_empty(aggressive=aggressive) 1670 1671 # 1672 # Cells 1673 # 1674 1675 def get_cells( 1676 self, 1677 coord: tuple | list | str | None = None, 1678 cell_type: str | None = None, 1679 style: str | None = None, 1680 content: str | None = None, 1681 flat: bool = False, 1682 ) -> list: 1683 """Get the cells matching the criteria. If 'coord' is None, 1684 parse the whole table, else parse the area defined by 'coord'. 1685 1686 Filter by cell_type = "all" will retrieve cells of any 1687 type, aka non empty cells. 1688 1689 If flat is True (default is False), the method return a single list 1690 of all the values, else a list of lists of cells. 1691 1692 if cell_type, style and content are None, get_cells() will return 1693 the exact number of cells of the area, including empty cells. 1694 1695 Arguments: 1696 1697 coordinates -- str or tuple of int : coordinates of area 1698 1699 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 1700 'currency', 'percentage' or 'all' 1701 1702 content -- str regex 1703 1704 style -- str 1705 1706 flat -- boolean 1707 1708 Return: list of tuples 1709 """ 1710 if coord: 1711 x, y, z, t = self._translate_table_coordinates(coord) 1712 else: 1713 x = y = z = t = None 1714 if flat: 1715 cells: list[Cell] = [] 1716 for row in self.traverse(start=y, end=t): 1717 row_cells = row.get_cells( 1718 coord=(x, z), 1719 cell_type=cell_type, 1720 style=style, 1721 content=content, 1722 ) 1723 cells.extend(row_cells) 1724 return cells 1725 else: 1726 lcells: list[list[Cell]] = [] 1727 for row in self.traverse(start=y, end=t): 1728 row_cells = row.get_cells( 1729 coord=(x, z), 1730 cell_type=cell_type, 1731 style=style, 1732 content=content, 1733 ) 1734 lcells.append(row_cells) 1735 return lcells 1736 1737 def get_cell( 1738 self, 1739 coord: tuple | list | str, 1740 clone: bool = True, 1741 keep_repeated: bool = True, 1742 ) -> Cell: 1743 """Get the cell at the given coordinates. 1744 1745 They are either a 2-uplet of (x, y) starting from 0, or a 1746 human-readable position like "C4". 1747 1748 A copy is returned, use ``set_cell`` to push it back. 1749 1750 Arguments: 1751 1752 coord -- (int, int) or str 1753 1754 Return: Cell 1755 """ 1756 x, y = self._translate_cell_coordinates(coord) 1757 if x is None: 1758 raise ValueError 1759 if y is None: 1760 raise ValueError 1761 # Outside the defined table 1762 if y >= self.height: 1763 cell = Cell() 1764 else: 1765 # Inside the defined table 1766 row = self._get_row2_base(y) 1767 if row is None: 1768 raise ValueError 1769 read_cell = row.get_cell(x, clone=clone) 1770 if read_cell is None: 1771 raise ValueError 1772 cell = read_cell 1773 if not keep_repeated: 1774 repeated = cell.repeated or 1 1775 if repeated >= 2: 1776 cell.repeated = None 1777 cell.x = x 1778 cell.y = y 1779 return cell 1780 1781 def get_value( 1782 self, 1783 coord: tuple | list | str, 1784 get_type: bool = False, 1785 ) -> Any: 1786 """Shortcut to get the Python value of the cell at the given 1787 coordinates. 1788 1789 If get_type is True, returns the tuples (value, ODF type) 1790 1791 coord is either a 2-uplet of (x, y) starting from 0, or a 1792 human-readable position like "C4". If an Area is given, the upper 1793 left position is used as coord. 1794 1795 Arguments: 1796 1797 coord -- (int, int) or str : coordinate 1798 1799 Return: Python type 1800 """ 1801 x, y = self._translate_cell_coordinates(coord) 1802 if x is None: 1803 raise ValueError 1804 if y is None: 1805 raise ValueError 1806 # Outside the defined table 1807 if y >= self.height: 1808 if get_type: 1809 return (None, None) 1810 return None 1811 else: 1812 # Inside the defined table 1813 row = self._get_row2_base(y) 1814 if row is None: 1815 raise ValueError 1816 cell = row._get_cell2_base(x) 1817 if cell is None: 1818 if get_type: 1819 return (None, None) 1820 return None 1821 return cell.get_value(get_type=get_type) 1822 1823 def set_cell( 1824 self, 1825 coord: tuple | list | str, 1826 cell: Cell | None = None, 1827 clone: bool = True, 1828 ) -> Cell: 1829 """Replace a cell of the table at the given coordinates. 1830 1831 They are either a 2-uplet of (x, y) starting from 0, or a 1832 human-readable position like "C4". 1833 1834 Arguments: 1835 1836 coord -- (int, int) or str : coordinate 1837 1838 cell -- Cell 1839 1840 return the cell, with x and y updated 1841 """ 1842 if cell is None: 1843 cell = Cell() 1844 clone = False 1845 x, y = self._translate_cell_coordinates(coord) 1846 if x is None: 1847 raise ValueError 1848 if y is None: 1849 raise ValueError 1850 cell.x = x 1851 cell.y = y 1852 if y >= self.height: 1853 row = Row() 1854 cell_back = row.set_cell(x, cell, clone=clone) 1855 self.set_row(y, row, clone=False) 1856 else: 1857 row_read = self._get_row2_base(y) 1858 if row_read is None: 1859 raise ValueError 1860 row = row_read 1861 row.y = y 1862 repeated = row.repeated or 1 1863 if repeated > 1: 1864 row = row.clone 1865 row.repeated = None 1866 cell_back = row.set_cell(x, cell, clone=clone) 1867 self.set_row(y, row, clone=False) 1868 else: 1869 cell_back = row.set_cell(x, cell, clone=clone) 1870 # Update width if necessary, since we don't use set_row 1871 self._update_width(row) 1872 return cell_back 1873 1874 def set_cells( 1875 self, 1876 cells: list[list[Cell]] | list[tuple[Cell]], 1877 coord: tuple | list | str | None = None, 1878 clone: bool = True, 1879 ) -> None: 1880 """Set the cells in the table, from the 'coord' position. 1881 1882 'coord' is the coordinate of the upper left cell to be modified by 1883 values. If 'coord' is None, default to the position (0,0) ("A1"). 1884 If 'coord' is an area (e.g. "A2:B5"), the upper left position of this 1885 area is used as coordinate. 1886 1887 The table is *not* cleared before the operation, to reset the table 1888 before setting cells, use table.clear(). 1889 1890 A list of lists is expected, with as many lists as rows to be set, and 1891 as many cell in each sublist as cells to be setted in the row. 1892 1893 Arguments: 1894 1895 cells -- list of list of cells 1896 1897 coord -- tuple or str 1898 1899 values -- list of lists of python types 1900 """ 1901 if coord: 1902 x, y = self._translate_cell_coordinates(coord) 1903 else: 1904 x = y = 0 1905 if y is None: 1906 y = 0 1907 if x is None: 1908 x = 0 1909 y -= 1 1910 for row_cells in cells: 1911 y += 1 1912 if not row_cells: 1913 continue 1914 row = self.get_row(y, clone=True) 1915 repeated = row.repeated or 1 1916 if repeated >= 2: 1917 row.repeated = None 1918 row.set_cells(row_cells, start=x, clone=clone) 1919 self.set_row(y, row, clone=False) 1920 self._update_width(row) 1921 1922 def set_value( 1923 self, 1924 coord: tuple | list | str, 1925 value: Any, 1926 cell_type: str | None = None, 1927 currency: str | None = None, 1928 style: str | None = None, 1929 ) -> None: 1930 """Set the Python value of the cell at the given coordinates. 1931 1932 They are either a 2-uplet of (x, y) starting from 0, or a 1933 human-readable position like "C4". 1934 1935 Arguments: 1936 1937 coord -- (int, int) or str 1938 1939 value -- Python type 1940 1941 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 1942 'string' or 'time' 1943 1944 currency -- three-letter str 1945 1946 style -- str 1947 1948 """ 1949 self.set_cell( 1950 coord, 1951 Cell(value, cell_type=cell_type, currency=currency, style=style), 1952 clone=False, 1953 ) 1954 1955 def set_cell_image( 1956 self, 1957 coord: tuple | list | str, 1958 image_frame: Frame, 1959 doc_type: str | None = None, 1960 ) -> None: 1961 """Do all the magic to display an image in the cell at the given 1962 coordinates. 1963 1964 They are either a 2-uplet of (x, y) starting from 0, or a 1965 human-readable position like "C4". 1966 1967 The frame element must contain the expected image position and 1968 dimensions. 1969 1970 DrawImage insertion depends on the document type, so the type must be 1971 provided or the table element must be already attached to a document. 1972 1973 Arguments: 1974 1975 coord -- (int, int) or str 1976 1977 image_frame -- Frame including an image 1978 1979 doc_type -- 'spreadsheet' or 'text' 1980 """ 1981 # Test document type 1982 if doc_type is None: 1983 body = self.document_body 1984 if body is None: 1985 raise ValueError("document type not found") 1986 doc_type = {"office:spreadsheet": "spreadsheet", "office:text": "text"}.get( 1987 body.tag 1988 ) 1989 if doc_type is None: 1990 raise ValueError("document type not supported for images") 1991 # We need the end address of the image 1992 x, y = self._translate_cell_coordinates(coord) 1993 if x is None: 1994 raise ValueError 1995 if y is None: 1996 raise ValueError 1997 cell = self.get_cell((x, y)) 1998 image_frame = image_frame.clone # type: ignore 1999 # Remove any previous paragraph, frame, etc. 2000 for child in cell.children: 2001 cell.delete(child) 2002 # Now it all depends on the document type 2003 if doc_type == "spreadsheet": 2004 image_frame.anchor_type = "char" 2005 # The frame needs end coordinates 2006 width, height = image_frame.size 2007 image_frame.set_attribute("table:end-x", width) 2008 image_frame.set_attribute("table:end-y", height) 2009 # FIXME what happens when the address changes? 2010 address = f"{self.name}.{digit_to_alpha(x)}{y + 1}" 2011 image_frame.set_attribute("table:end-cell-address", address) 2012 # The frame is directly in the cell 2013 cell.append(image_frame) 2014 elif doc_type == "text": 2015 # The frame must be in a paragraph 2016 cell.set_value("") 2017 paragraph = cell.get_element("text:p") 2018 if paragraph is None: 2019 raise ValueError 2020 paragraph.append(image_frame) 2021 self.set_cell(coord, cell) 2022 2023 def insert_cell( 2024 self, 2025 coord: tuple | list | str, 2026 cell: Cell | None = None, 2027 clone: bool = True, 2028 ) -> Cell: 2029 """Insert the given cell at the given coordinates. If no cell is 2030 given, an empty one is created. 2031 2032 Coordinates are either a 2-uplet of (x, y) starting from 0, or a 2033 human-readable position like "C4". 2034 2035 Cells on the right are shifted. Other rows remain untouched. 2036 2037 Arguments: 2038 2039 coord -- (int, int) or str 2040 2041 cell -- Cell 2042 2043 returns the cell with x and y updated 2044 """ 2045 if cell is None: 2046 cell = Cell() 2047 clone = False 2048 if clone: 2049 cell = cell.clone 2050 x, y = self._translate_cell_coordinates(coord) 2051 if x is None: 2052 raise ValueError 2053 if y is None: 2054 raise ValueError 2055 row = self._get_row2(y, clone=True) 2056 row.y = y 2057 row.repeated = None 2058 cell_back = row.insert_cell(x, cell, clone=False) 2059 self.set_row(y, row, clone=False) 2060 # Update width if necessary 2061 self._update_width(row) 2062 return cell_back 2063 2064 def append_cell( 2065 self, 2066 y: int | str, 2067 cell: Cell | None = None, 2068 clone: bool = True, 2069 ) -> Cell: 2070 """Append the given cell at the "y" coordinate. Repeated cells are 2071 accepted. If no cell is given, an empty one is created. 2072 2073 Position start at 0. So cell A4 is on row 3. 2074 2075 Other rows remain untouched. 2076 2077 Arguments: 2078 2079 y -- int or str 2080 2081 cell -- Cell 2082 2083 returns the cell with x and y updated 2084 """ 2085 if cell is None: 2086 cell = Cell() 2087 clone = False 2088 if clone: 2089 cell = cell.clone 2090 y = self._translate_y_from_any(y) 2091 row = self._get_row2(y) 2092 row.y = y 2093 cell_back = row.append_cell(cell, clone=False) 2094 self.set_row(y, row) 2095 # Update width if necessary 2096 self._update_width(row) 2097 return cell_back 2098 2099 def delete_cell(self, coord: tuple | list | str) -> None: 2100 """Delete the cell at the given coordinates, so that next cells are 2101 shifted to the left. 2102 2103 Coordinates are either a 2-uplet of (x, y) starting from 0, or a 2104 human-readable position like "C4". 2105 2106 Use set_value() for erasing value. 2107 2108 Arguments: 2109 2110 coord -- (int, int) or str 2111 """ 2112 x, y = self._translate_cell_coordinates(coord) 2113 if x is None: 2114 raise ValueError 2115 if y is None: 2116 raise ValueError 2117 # Outside the defined table 2118 if y >= self.height: 2119 return 2120 # Inside the defined table 2121 row = self._get_row2_base(y) 2122 if row is None: 2123 raise ValueError 2124 row.delete_cell(x) 2125 # self.set_row(y, row) 2126 2127 # Columns 2128 2129 def _get_columns(self) -> list: 2130 return self.get_elements(_xpath_column) 2131 2132 def traverse_columns( # noqa: C901 2133 self, 2134 start: int | None = None, 2135 end: int | None = None, 2136 ) -> Iterator[Column]: 2137 """Yield as many column elements as expected columns in the table, 2138 i.e. expand repetitions by returning the same column as many times as 2139 necessary. 2140 2141 Arguments: 2142 2143 start -- int 2144 2145 end -- int 2146 2147 Copies are returned, use set_column() to push them back. 2148 """ 2149 idx = -1 2150 before = -1 2151 x = 0 2152 if start is None and end is None: 2153 for juska in self._cmap: 2154 idx += 1 2155 if idx in self._indexes["_cmap"]: 2156 column = self._indexes["_cmap"][idx] 2157 else: 2158 column = self._get_element_idx2(_xpath_column_idx, idx) 2159 self._indexes["_cmap"][idx] = column 2160 repeated = juska - before 2161 before = juska 2162 for _i in range(repeated or 1): 2163 # Return a copy without the now obsolete repetition 2164 column = column.clone 2165 column.x = x 2166 x += 1 2167 if repeated > 1: 2168 column.repeated = None 2169 yield column 2170 else: 2171 if start is None: 2172 start = 0 2173 start = max(0, start) 2174 if end is None: 2175 try: 2176 end = self._cmap[-1] 2177 except Exception: 2178 end = -1 2179 start_map = find_odf_idx(self._cmap, start) 2180 if start_map is None: 2181 return 2182 if start_map > 0: 2183 before = self._cmap[start_map - 1] 2184 idx = start_map - 1 2185 before = start - 1 2186 x = start 2187 for juska in self._cmap[start_map:]: 2188 idx += 1 2189 if idx in self._indexes["_cmap"]: 2190 column = self._indexes["_cmap"][idx] 2191 else: 2192 column = self._get_element_idx2(_xpath_column_idx, idx) 2193 self._indexes["_cmap"][idx] = column 2194 repeated = juska - before 2195 before = juska 2196 for _i in range(repeated or 1): 2197 if x <= end: 2198 column = column.clone 2199 column.x = x 2200 x += 1 2201 if repeated > 1 or (x == start and start > 0): 2202 column.repeated = None 2203 yield column 2204 2205 def get_columns( 2206 self, 2207 coord: tuple | list | str | None = None, 2208 style: str | None = None, 2209 ) -> list[Column]: 2210 """Get the list of columns matching the criteria. Each result is a 2211 tuple of (x, column). 2212 2213 Arguments: 2214 2215 coord -- str or tuple of int : coordinates of columns 2216 2217 style -- str 2218 2219 Return: list of columns 2220 """ 2221 if coord: 2222 x, _y, _z, t = self._translate_column_coordinates(coord) 2223 else: 2224 x = t = None 2225 if not style: 2226 return list(self.traverse_columns(start=x, end=t)) 2227 columns = [] 2228 for column in self.traverse_columns(start=x, end=t): 2229 if style != column.style: 2230 continue 2231 columns.append(column) 2232 return columns 2233 2234 def _get_column2(self, x: int) -> Column | None: 2235 # Outside the defined table 2236 if x >= self.width: 2237 return Column() 2238 # Inside the defined table 2239 odf_idx = find_odf_idx(self._cmap, x) 2240 if odf_idx is not None: 2241 column = self._get_element_idx2(_xpath_column_idx, odf_idx) 2242 if column is None: 2243 return None 2244 # fixme : no clone here => change doc and unit tests 2245 return column.clone # type: ignore 2246 # return row 2247 return None 2248 2249 def get_column(self, x: int | str) -> Column: 2250 """Get the column at the given "x" position. 2251 2252 ODF columns don't contain cells, only style information. 2253 2254 Position start at 0. So cell C4 is on column 2. Alphabetical position 2255 like "C" is accepted. 2256 2257 A copy is returned, use set_column() to push it back. 2258 2259 Arguments: 2260 2261 x -- int or str 2262 2263 Return: Column 2264 """ 2265 x = self._translate_x_from_any(x) 2266 column = self._get_column2(x) 2267 if column is None: 2268 raise ValueError 2269 column.x = x 2270 return column 2271 2272 def set_column( 2273 self, 2274 x: int | str, 2275 column: Column | None = None, 2276 ) -> Column: 2277 """Replace the column at the given "x" position. 2278 2279 ODF columns don't contain cells, only style information. 2280 2281 Position start at 0. So cell C4 is on column 2. Alphabetical position 2282 like "C" is accepted. 2283 2284 Arguments: 2285 2286 x -- int or str 2287 2288 column -- Column 2289 """ 2290 x = self._translate_x_from_any(x) 2291 if column is None: 2292 column = Column() 2293 repeated = 1 2294 else: 2295 repeated = column.repeated or 1 2296 column.x = x 2297 # Outside the defined table ? 2298 diff = x - self.width 2299 if diff == 0: 2300 column_back = self.append_column(column, _repeated=repeated) 2301 elif diff > 0: 2302 self.append_column(Column(repeated=diff), _repeated=diff) 2303 column_back = self.append_column(column, _repeated=repeated) 2304 else: 2305 # Inside the defined table 2306 column_back = set_item_in_vault( # type: ignore 2307 x, column, self, _xpath_column_idx, "_cmap" 2308 ) 2309 return column_back 2310 2311 def insert_column( 2312 self, 2313 x: int | str, 2314 column: Column | None = None, 2315 ) -> Column: 2316 """Insert the column before the given "x" position. If no column is 2317 given, an empty one is created. 2318 2319 ODF columns don't contain cells, only style information. 2320 2321 Position start at 0. So cell C4 is on column 2. Alphabetical position 2322 like "C" is accepted. 2323 2324 Arguments: 2325 2326 x -- int or str 2327 2328 column -- Column 2329 """ 2330 if column is None: 2331 column = Column() 2332 x = self._translate_x_from_any(x) 2333 diff = x - self.width 2334 if diff < 0: 2335 column_back = insert_item_in_vault( 2336 x, column, self, _xpath_column_idx, "_cmap" 2337 ) 2338 elif diff == 0: 2339 column_back = self.append_column(column.clone) 2340 else: 2341 self.append_column(Column(repeated=diff), _repeated=diff) 2342 column_back = self.append_column(column.clone) 2343 column_back.x = x # type: ignore 2344 # Repetitions are accepted 2345 repeated = column.repeated or 1 2346 # Update width on every row 2347 for row in self._get_rows(): 2348 if row.width > x: 2349 row.insert_cell(x, Cell(repeated=repeated)) 2350 # Shorter rows don't need insert 2351 # Longer rows shouldn't exist! 2352 return column_back # type: ignore 2353 2354 def append_column( 2355 self, 2356 column: Column | None = None, 2357 _repeated: int | None = None, 2358 ) -> Column: 2359 """Append the column at the end of the table. If no column is given, 2360 an empty one is created. 2361 2362 ODF columns don't contain cells, only style information. 2363 2364 Position start at 0. So cell C4 is on column 2. Alphabetical position 2365 like "C" is accepted. 2366 2367 Arguments: 2368 2369 column -- Column 2370 """ 2371 if column is None: 2372 column = Column() 2373 else: 2374 column = column.clone 2375 if not self._cmap: 2376 position = 0 2377 else: 2378 odf_idx = len(self._cmap) - 1 2379 last_column = self._get_element_idx2(_xpath_column_idx, odf_idx) 2380 if last_column is None: 2381 raise ValueError 2382 position = self.index(last_column) + 1 2383 column.x = self.width 2384 self.insert(column, position=position) 2385 # Repetitions are accepted 2386 if _repeated is None: 2387 _repeated = column.repeated or 1 2388 self._cmap = insert_map_once(self._cmap, len(self._cmap), _repeated) 2389 # No need to update row widths 2390 return column 2391 2392 def delete_column(self, x: int | str) -> None: 2393 """Delete the column at the given position. ODF columns don't contain 2394 cells, only style information. 2395 2396 Position start at 0. So cell C4 is on column 2. Alphabetical position 2397 like "C" is accepted. 2398 2399 Arguments: 2400 2401 x -- int or str 2402 """ 2403 x = self._translate_x_from_any(x) 2404 # Outside the defined table 2405 if x >= self.width: 2406 return 2407 # Inside the defined table 2408 delete_item_in_vault(x, self, _xpath_column_idx, "_cmap") 2409 # Update width 2410 width = self.width 2411 for row in self._get_rows(): 2412 if row.width >= width: 2413 row.delete_cell(x) 2414 2415 def get_column_cells( # noqa: C901 2416 self, 2417 x: int | str, 2418 style: str | None = None, 2419 content: str | None = None, 2420 cell_type: str | None = None, 2421 complete: bool = False, 2422 ) -> list[Cell | None]: 2423 """Get the list of cells at the given position. 2424 2425 Position start at 0. So cell C4 is on column 2. Alphabetical position 2426 like "C" is accepted. 2427 2428 Filter by cell_type, with cell_type 'all' will retrieve cells of any 2429 type, aka non empty cells. 2430 2431 If complete is True, replace missing values by None. 2432 2433 Arguments: 2434 2435 x -- int or str 2436 2437 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 2438 'currency', 'percentage' or 'all' 2439 2440 content -- str regex 2441 2442 style -- str 2443 2444 complete -- boolean 2445 2446 Return: list of Cell 2447 """ 2448 x = self._translate_x_from_any(x) 2449 if cell_type: 2450 cell_type = cell_type.lower().strip() 2451 cells: list[Cell | None] = [] 2452 if not style and not content and not cell_type: 2453 for row in self.traverse(): 2454 cells.append(row.get_cell(x, clone=True)) 2455 return cells 2456 for row in self.traverse(): 2457 cell = row.get_cell(x, clone=True) 2458 if cell is None: 2459 raise ValueError 2460 # Filter the cells by cell_type 2461 if cell_type: 2462 ctype = cell.type 2463 if not ctype or not (ctype == cell_type or cell_type == "all"): 2464 if complete: 2465 cells.append(None) 2466 continue 2467 # Filter the cells with the regex 2468 if content and not cell.match(content): 2469 if complete: 2470 cells.append(None) 2471 continue 2472 # Filter the cells with the style 2473 if style and style != cell.style: 2474 if complete: 2475 cells.append(None) 2476 continue 2477 cells.append(cell) 2478 return cells 2479 2480 def get_column_values( 2481 self, 2482 x: int | str, 2483 cell_type: str | None = None, 2484 complete: bool = True, 2485 get_type: bool = False, 2486 ) -> list[Any]: 2487 """Shortcut to get the list of Python values for the cells at the 2488 given position. 2489 2490 Position start at 0. So cell C4 is on column 2. Alphabetical position 2491 like "C" is accepted. 2492 2493 Filter by cell_type, with cell_type 'all' will retrieve cells of any 2494 type, aka non empty cells. 2495 If cell_type and complete is True, replace missing values by None. 2496 2497 If get_type is True, returns a tuple (value, ODF type of value) 2498 2499 Arguments: 2500 2501 x -- int or str 2502 2503 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 2504 'currency', 'percentage' or 'all' 2505 2506 complete -- boolean 2507 2508 get_type -- boolean 2509 2510 Return: list of Python types 2511 """ 2512 cells = self.get_column_cells( 2513 x, style=None, content=None, cell_type=cell_type, complete=complete 2514 ) 2515 values: list[Any] = [] 2516 for cell in cells: 2517 if cell is None: 2518 if complete: 2519 if get_type: 2520 values.append((None, None)) 2521 else: 2522 values.append(None) 2523 continue 2524 if cell_type: 2525 ctype = cell.type 2526 if not ctype or not (ctype == cell_type or cell_type == "all"): 2527 if complete: 2528 if get_type: 2529 values.append((None, None)) 2530 else: 2531 values.append(None) 2532 continue 2533 values.append(cell.get_value(get_type=get_type)) 2534 return values 2535 2536 def set_column_cells(self, x: int | str, cells: list[Cell]) -> None: 2537 """Shortcut to set the list of cells at the given position. 2538 2539 Position start at 0. So cell C4 is on column 2. Alphabetical position 2540 like "C" is accepted. 2541 2542 The list must have the same length than the table height. 2543 2544 Arguments: 2545 2546 x -- int or str 2547 2548 cells -- list of Cell 2549 """ 2550 height = self.height 2551 if len(cells) != height: 2552 raise ValueError(f"col mismatch: {height} cells expected") 2553 cells_iterator = iter(cells) 2554 for y, row in enumerate(self.traverse()): 2555 row.set_cell(x, next(cells_iterator)) 2556 self.set_row(y, row) 2557 2558 def set_column_values( 2559 self, 2560 x: int | str, 2561 values: list, 2562 cell_type: str | None = None, 2563 currency: str | None = None, 2564 style: str | None = None, 2565 ) -> None: 2566 """Shortcut to set the list of Python values of cells at the given 2567 position. 2568 2569 Position start at 0. So cell C4 is on column 2. Alphabetical position 2570 like "C" is accepted. 2571 2572 The list must have the same length than the table height. 2573 2574 Arguments: 2575 2576 x -- int or str 2577 2578 values -- list of Python types 2579 2580 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 2581 'string' or 'time' 2582 2583 currency -- three-letter str 2584 2585 style -- str 2586 """ 2587 cells = [ 2588 Cell(value, cell_type=cell_type, currency=currency, style=style) 2589 for value in values 2590 ] 2591 self.set_column_cells(x, cells) 2592 2593 def is_column_empty(self, x: int | str, aggressive: bool = False) -> bool: 2594 """Return wether every cell in the column at "x" position has no value 2595 or the value evaluates to False (empty string), and no style. 2596 2597 Position start at 0. So cell C4 is on column 2. Alphabetical position 2598 like "C" is accepted. 2599 2600 If aggressive is True, empty cells with style are considered empty. 2601 2602 Return: bool 2603 """ 2604 for cell in self.get_column_cells(x): 2605 if cell is None: 2606 continue 2607 if not cell.is_empty(aggressive=aggressive): 2608 return False 2609 return True 2610 2611 # Named Range 2612 2613 def get_named_ranges( # type: ignore 2614 self, 2615 table_name: str | list[str] | None = None, 2616 ) -> list[NamedRange]: 2617 """Returns the list of available Name Ranges of the spreadsheet. If 2618 table_name is provided, limits the search to these tables. 2619 Beware : named ranges are stored at the body level, thus do not call 2620 this method on a cloned table. 2621 2622 Arguments: 2623 2624 table_names -- str or list of str, names of tables 2625 2626 Return : list of table_range 2627 """ 2628 body = self.document_body 2629 if not body: 2630 return [] 2631 all_named_ranges = body.get_named_ranges() 2632 if not table_name: 2633 return all_named_ranges # type:ignore 2634 filter_ = [] 2635 if isinstance(table_name, str): 2636 filter_.append(table_name) 2637 elif isiterable(table_name): 2638 filter_.extend(table_name) 2639 else: 2640 raise ValueError( 2641 f"table_name must be string or Iterable, not {type(table_name)}" 2642 ) 2643 return [ 2644 nr for nr in all_named_ranges if nr.table_name in filter_ # type:ignore 2645 ] 2646 2647 def get_named_range(self, name: str) -> NamedRange: 2648 """Returns the Name Ranges of the specified name. If 2649 table_name is provided, limits the search to these tables. 2650 Beware : named ranges are stored at the body level, thus do not call 2651 this method on a cloned table. 2652 2653 Arguments: 2654 2655 name -- str, name of the named range object 2656 2657 Return : NamedRange 2658 """ 2659 body = self.document_body 2660 if not body: 2661 raise ValueError("Table is not inside a document") 2662 return body.get_named_range(name) # type: ignore 2663 2664 def set_named_range( 2665 self, 2666 name: str, 2667 crange: str | tuple | list, 2668 table_name: str | None = None, 2669 usage: str | None = None, 2670 ) -> None: 2671 """Create a Named Range element and insert it in the document. 2672 Beware : named ranges are stored at the body level, thus do not call 2673 this method on a cloned table. 2674 2675 Arguments: 2676 2677 name -- str, name of the named range 2678 2679 crange -- str or tuple of int, cell or area coordinate 2680 2681 table_name -- str, name of the table 2682 2683 uage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row' 2684 """ 2685 body = self.document_body 2686 if not body: 2687 raise ValueError("Table is not inside a document") 2688 if not name: 2689 raise ValueError("Name required.") 2690 if table_name is None: 2691 table_name = self.name 2692 named_range = NamedRange(name, crange, table_name, usage) 2693 body.append_named_range(named_range) 2694 2695 def delete_named_range(self, name: str) -> None: 2696 """Delete the Named Range of specified name from the spreadsheet. 2697 Beware : named ranges are stored at the body level, thus do not call 2698 this method on a cloned table. 2699 2700 Arguments: 2701 2702 name -- str 2703 """ 2704 name = name.strip() 2705 if not name: 2706 raise ValueError("Name required.") 2707 body = self.document_body 2708 if not body: 2709 raise ValueError("Table is not inside a document.") 2710 body.delete_named_range(name) 2711 2712 # 2713 # Cell span 2714 # 2715 2716 def set_span( # noqa: C901 2717 self, 2718 area: str | tuple | list, 2719 merge: bool = False, 2720 ) -> bool: 2721 """Create a Cell Span : span the first cell of the area on several 2722 columns and/or rows. 2723 If merge is True, replace text of the cell by the concatenation of 2724 existing text in covered cells. 2725 Beware : if merge is True, old text is changed, if merge is False 2726 (the default), old text in coverd cells is still present but not 2727 displayed by most GUI. 2728 2729 If the area defines only one cell, the set span will do nothing. 2730 It is not allowed to apply set span to an area whose one cell already 2731 belongs to previous cell span. 2732 2733 Area can be either one cell (like 'A1') or an area ('A1:B2'). It can 2734 be provided as an alpha numeric value like "A1:B2' or a tuple like 2735 (0, 0, 1, 1) or (0, 0). 2736 2737 Arguments: 2738 2739 area -- str or tuple of int, cell or area coordinate 2740 2741 merge -- boolean 2742 """ 2743 # get area 2744 digits = convert_coordinates(area) 2745 if len(digits) == 4: 2746 x, y, z, t = digits 2747 else: 2748 x, y = digits 2749 z, t = digits 2750 start = x, y 2751 end = z, t 2752 if start == end: 2753 # one cell : do nothing 2754 return False 2755 if x is None: 2756 raise ValueError 2757 if y is None: 2758 raise ValueError 2759 if z is None: 2760 raise ValueError 2761 if t is None: 2762 raise ValueError 2763 # check for previous span 2764 good = True 2765 # Check boundaries and empty cells : need to crate non existent cells 2766 # so don't use get_cells directly, but get_cell 2767 cells = [] 2768 for yy in range(y, t + 1): 2769 row_cells = [] 2770 for xx in range(x, z + 1): 2771 row_cells.append( 2772 self.get_cell((xx, yy), clone=True, keep_repeated=False) 2773 ) 2774 cells.append(row_cells) 2775 for row in cells: 2776 for cell in row: 2777 if cell.is_spanned(): 2778 good = False 2779 break 2780 if not good: 2781 break 2782 if not good: 2783 return False 2784 # Check boundaries 2785 # if z >= self.width or t >= self.height: 2786 # self.set_cell(coord = end) 2787 # print area, z, t 2788 # cells = self.get_cells((x, y, z, t)) 2789 # print cells 2790 # do it: 2791 if merge: 2792 val_list = [] 2793 for row in cells: 2794 for cell in row: 2795 if cell.is_empty(aggressive=True): 2796 continue 2797 val = cell.get_value() 2798 if val is not None: 2799 if isinstance(val, str): 2800 val.strip() 2801 if val != "": 2802 val_list.append(val) 2803 cell.clear() 2804 if val_list: 2805 if len(val_list) == 1: 2806 cells[0][0].set_value(val_list[0]) 2807 else: 2808 value = " ".join([str(v) for v in val_list if v]) 2809 cells[0][0].set_value(value) 2810 cols = z - x + 1 2811 cells[0][0].set_attribute("table:number-columns-spanned", str(cols)) 2812 rows = t - y + 1 2813 cells[0][0].set_attribute("table:number-rows-spanned", str(rows)) 2814 for cell in cells[0][1:]: 2815 cell.tag = "table:covered-table-cell" 2816 for row in cells[1:]: 2817 for cell in row: 2818 cell.tag = "table:covered-table-cell" 2819 # replace cells in table 2820 self.set_cells(cells, coord=start, clone=False) 2821 return True 2822 2823 def del_span(self, area: str | tuple | list) -> bool: 2824 """Delete a Cell Span. 'area' is the cell coordiante of the upper left 2825 cell of the spanned area. 2826 2827 Area can be either one cell (like 'A1') or an area ('A1:B2'). It can 2828 be provided as an alpha numeric value like "A1:B2' or a tuple like 2829 (0, 0, 1, 1) or (0, 0). If an area is provided, the upper left cell 2830 is used. 2831 2832 Arguments: 2833 2834 area -- str or tuple of int, cell or area coordinate 2835 """ 2836 # get area 2837 digits = convert_coordinates(area) 2838 if len(digits) == 4: 2839 x, y, _z, _t = digits 2840 else: 2841 x, y = digits 2842 if x is None: 2843 raise ValueError 2844 if y is None: 2845 raise ValueError 2846 start = x, y 2847 # check for previous span 2848 cell0 = self.get_cell(start) 2849 nb_cols = cell0.get_attribute_integer("table:number-columns-spanned") 2850 if nb_cols is None: 2851 return False 2852 nb_rows = cell0.get_attribute_integer("table:number-rows-spanned") 2853 if nb_rows is None: 2854 return False 2855 z = x + nb_cols - 1 2856 t = y + nb_rows - 1 2857 cells = self.get_cells((x, y, z, t)) 2858 cells[0][0].del_attribute("table:number-columns-spanned") 2859 cells[0][0].del_attribute("table:number-rows-spanned") 2860 for cell in cells[0][1:]: 2861 cell.tag = "table:table-cell" 2862 for row in cells[1:]: 2863 for cell in row: 2864 cell.tag = "table:table-cell" 2865 # replace cells in table 2866 self.set_cells(cells, coord=start, clone=False) 2867 return True 2868 2869 # Utilities 2870 2871 def to_csv( 2872 self, 2873 path_or_file: str | Path | None = None, 2874 dialect: str = "excel", 2875 ) -> Any: 2876 """Write the table as CSV in the file. 2877 2878 If the file is a string, it is opened as a local path. Else an 2879 opened file-like is expected. 2880 2881 Arguments: 2882 2883 path_or_file -- str or file-like 2884 2885 dialect -- str, python csv.dialect, can be 'excel', 'unix'... 2886 """ 2887 2888 def write_content(csv_writer: object) -> None: 2889 for values in self.iter_values(): 2890 line = [] 2891 for value in values: 2892 if value is None: 2893 value = "" 2894 if isinstance(value, str): 2895 value = value.strip() 2896 line.append(value) 2897 csv_writer.writerow(line) # type: ignore 2898 2899 out = StringIO(newline="") 2900 csv_writer = csv.writer(out, dialect=dialect) 2901 write_content(csv_writer) 2902 if path_or_file is None: 2903 return out.getvalue() 2904 path = Path(path_or_file) 2905 path.write_text(out.getvalue()) 2906 return None
ODF table "table:table"
294 def __init__( 295 self, 296 name: str | None = None, 297 width: int | None = None, 298 height: int | None = None, 299 protected: bool = False, 300 protection_key: str | None = None, 301 display: bool = True, 302 printable: bool = True, 303 print_ranges: list[str] | None = None, 304 style: str | None = None, 305 **kwargs: Any, 306 ) -> None: 307 """Create a table element, optionally prefilled with "height" rows of 308 "width" cells each. 309 310 If the table is to be protected, a protection key must be provided, 311 i.e. a hash value of the password. 312 313 If the table must not be displayed, set "display" to False. 314 315 If the table must not be printed, set "printable" to False. The table 316 will not be printed when it is not displayed, whatever the value of 317 this argument. 318 319 Ranges of cells to print can be provided as a list of cell ranges, 320 e.g. ['E6:K12', 'P6:R12'] or directly as a raw string, e.g. 321 "E6:K12 P6:R12". 322 323 You can access and modify the XML tree manually, but you probably want 324 to use the API to access and alter cells. It will save you from 325 handling repetitions and the same number of cells for each row. 326 327 If you use both the table API and the XML API, you are on your own for 328 ensuiring model integrity. 329 330 Arguments: 331 332 name -- str 333 334 width -- int 335 336 height -- int 337 338 protected -- bool 339 340 protection_key -- str 341 342 display -- bool 343 344 printable -- bool 345 346 print_ranges -- list 347 348 style -- str 349 """ 350 super().__init__(**kwargs) 351 self._indexes = {} 352 self._indexes["_cmap"] = {} 353 self._indexes["_tmap"] = {} 354 if self._do_init: 355 self.name = name 356 if protected: 357 self.protected = protected 358 self.set_protection_key = protection_key 359 if not display: 360 self.displayed = display 361 if not printable: 362 self.printable = printable 363 if print_ranges: 364 self.print_ranges = print_ranges 365 if style: 366 self.style = style 367 # Prefill the table 368 if width is not None or height is not None: 369 width = width or 1 370 height = height or 1 371 # Column groups for style information 372 columns = Column(repeated=width) 373 self._append(columns) 374 for _i in range(height): 375 row = Row(width) 376 self._append(row) 377 self._compute_table_cache()
Create a table element, optionally prefilled with "height" rows of "width" cells each.
If the table is to be protected, a protection key must be provided, i.e. a hash value of the password.
If the table must not be displayed, set "display" to False.
If the table must not be printed, set "printable" to False. The table will not be printed when it is not displayed, whatever the value of this argument.
Ranges of cells to print can be provided as a list of cell ranges, e.g. ['E6:K12', 'P6:R12'] or directly as a raw string, e.g. "E6:K12 P6:R12".
You can access and modify the XML tree manually, but you probably want to use the API to access and alter cells. It will save you from handling repetitions and the same number of cells for each row.
If you use both the table API and the XML API, you are on your own for ensuiring model integrity.
Arguments:
name -- str
width -- int
height -- int
protected -- bool
protection_key -- str
display -- bool
printable -- bool
print_ranges -- list
style -- str
734 def append(self, something: Element | str) -> None: 735 """Dispatch .append() call to append_row() or append_column().""" 736 if isinstance(something, Row): 737 self.append_row(something) 738 elif isinstance(something, Column): 739 self.append_column(something) 740 else: 741 # probably still an error 742 self._append(something)
Dispatch .append() call to append_row() or append_column().
744 @property 745 def height(self) -> int: 746 """Get the current height of the table. 747 748 Return: int 749 """ 750 try: 751 height = self._tmap[-1] + 1 752 except Exception: 753 height = 0 754 return height
Get the current height of the table.
Return: int
756 @property 757 def width(self) -> int: 758 """Get the current width of the table, measured on columns. 759 760 Rows may have different widths, use the Table API to ensure width 761 consistency. 762 763 Return: int 764 """ 765 # Columns are our reference for user expected width 766 767 try: 768 width = self._cmap[-1] + 1 769 except Exception: 770 width = 0 771 772 # columns = self._get_columns() 773 # repeated = self.xpath( 774 # 'table:table-column/@table:number-columns-repeated') 775 # unrepeated = len(columns) - len(repeated) 776 # ws = sum(int(r) for r in repeated) + unrepeated 777 # if w != ws: 778 # print "WARNING ws", ws, "w", w 779 780 return width
Get the current width of the table, measured on columns.
Rows may have different widths, use the Table API to ensure width consistency.
Return: int
782 @property 783 def size(self) -> tuple[int, int]: 784 """Shortcut to get the current width and height of the table. 785 786 Return: (int, int) 787 """ 788 return self.width, self.height
Shortcut to get the current width and height of the table.
Return: (int, int)
790 @property 791 def name(self) -> str | None: 792 """Get / set the name of the table.""" 793 return self.get_attribute_string("table:name")
Get / set the name of the table.
854 @property 855 def style(self) -> str | None: 856 """Get / set the style of the table 857 858 Return: str 859 """ 860 return self.get_attribute_string("table:style-name")
Get / set the style of the table
Return: str
866 def get_formatted_text(self, context: dict | None = None) -> str: 867 if context and context["rst_mode"]: 868 return self._get_formatted_text_rst(context) 869 return self._get_formatted_text_normal(context)
This function should return a beautiful version of the text.
871 def get_values( 872 self, 873 coord: tuple | list | str | None = None, 874 cell_type: str | None = None, 875 complete: bool = True, 876 get_type: bool = False, 877 flat: bool = False, 878 ) -> list: 879 """Get a matrix of values of the table. 880 881 Filter by coordinates will parse the area defined by the coordinates. 882 883 If 'cell_type' is used and 'complete' is True (default), missing values 884 are replaced by None. 885 Filter by ' cell_type = "all" ' will retrieve cells of any 886 type, aka non empty cells. 887 888 If 'cell_type' is None, complete is always True : with no cell type 889 queried, get_values() returns None for each empty cell, the length 890 each lists is equal to the width of the table. 891 892 If get_type is True, returns tuples (value, ODF type of value), or 893 (None, None) for empty cells with complete True. 894 895 If flat is True, the methods return a single list of all the values. 896 By default, flat is False. 897 898 Arguments: 899 900 coord -- str or tuple of int : coordinates of area 901 902 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 903 'currency', 'percentage' or 'all' 904 905 complete -- boolean 906 907 get_type -- boolean 908 909 Return: list of lists of Python types 910 """ 911 if coord: 912 x, y, z, t = self._translate_table_coordinates(coord) 913 else: 914 x = y = z = t = None 915 data = [] 916 for row in self.traverse(start=y, end=t): 917 if z is None: 918 width = self.width 919 else: 920 width = min(z + 1, self.width) 921 if x is not None: 922 width -= x 923 values = row.get_values( 924 (x, z), 925 cell_type=cell_type, 926 complete=complete, 927 get_type=get_type, 928 ) 929 # complete row to match request width 930 if complete: 931 if get_type: 932 values.extend([(None, None)] * (width - len(values))) 933 else: 934 values.extend([None] * (width - len(values))) 935 if flat: 936 data.extend(values) 937 else: 938 data.append(values) 939 return data
Get a matrix of values of the table.
Filter by coordinates will parse the area defined by the coordinates.
If 'cell_type' is used and 'complete' is True (default), missing values are replaced by None. Filter by ' cell_type = "all" ' will retrieve cells of any type, aka non empty cells.
If 'cell_type' is None, complete is always True : with no cell type queried, get_values() returns None for each empty cell, the length each lists is equal to the width of the table.
If get_type is True, returns tuples (value, ODF type of value), or (None, None) for empty cells with complete True.
If flat is True, the methods return a single list of all the values. By default, flat is False.
Arguments:
coord -- str or tuple of int : coordinates of area
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency', 'percentage' or 'all'
complete -- boolean
get_type -- boolean
Return: list of lists of Python types
941 def iter_values( 942 self, 943 coord: tuple | list | str | None = None, 944 cell_type: str | None = None, 945 complete: bool = True, 946 get_type: bool = False, 947 ) -> Iterator[list]: 948 """Iterate through lines of Python values of the table. 949 950 Filter by coordinates will parse the area defined by the coordinates. 951 952 cell_type, complete, grt_type : see get_values() 953 954 955 956 Arguments: 957 958 coord -- str or tuple of int : coordinates of area 959 960 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 961 'currency', 'percentage' or 'all' 962 963 complete -- boolean 964 965 get_type -- boolean 966 967 Return: iterator of lists 968 """ 969 if coord: 970 x, y, z, t = self._translate_table_coordinates(coord) 971 else: 972 x = y = z = t = None 973 for row in self.traverse(start=y, end=t): 974 if z is None: 975 width = self.width 976 else: 977 width = min(z + 1, self.width) 978 if x is not None: 979 width -= x 980 values = row.get_values( 981 (x, z), 982 cell_type=cell_type, 983 complete=complete, 984 get_type=get_type, 985 ) 986 # complete row to match column width 987 if complete: 988 if get_type: 989 values.extend([(None, None)] * (width - len(values))) 990 else: 991 values.extend([None] * (width - len(values))) 992 yield values
Iterate through lines of Python values of the table.
Filter by coordinates will parse the area defined by the coordinates.
cell_type, complete, grt_type : see get_values()
Arguments:
coord -- str or tuple of int : coordinates of area
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency', 'percentage' or 'all'
complete -- boolean
get_type -- boolean
Return: iterator of lists
994 def set_values( 995 self, 996 values: list, 997 coord: tuple | list | str | None = None, 998 style: str | None = None, 999 cell_type: str | None = None, 1000 currency: str | None = None, 1001 ) -> None: 1002 """Set the value of cells in the table, from the 'coord' position 1003 with values. 1004 1005 'coord' is the coordinate of the upper left cell to be modified by 1006 values. If 'coord' is None, default to the position (0,0) ("A1"). 1007 If 'coord' is an area (e.g. "A2:B5"), the upper left position of this 1008 area is used as coordinate. 1009 1010 The table is *not* cleared before the operation, to reset the table 1011 before setting values, use table.clear(). 1012 1013 A list of lists is expected, with as many lists as rows, and as many 1014 items in each sublist as cells to be setted. None values in the list 1015 will create empty cells with no cell type (but eventually a style). 1016 1017 Arguments: 1018 1019 coord -- tuple or str 1020 1021 values -- list of lists of python types 1022 1023 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 1024 'string' or 'time' 1025 1026 currency -- three-letter str 1027 1028 style -- str 1029 """ 1030 if coord: 1031 x, y = self._translate_cell_coordinates(coord) 1032 else: 1033 x = y = 0 1034 if y is None: 1035 y = 0 1036 if x is None: 1037 x = 0 1038 y -= 1 1039 for row_values in values: 1040 y += 1 1041 if not row_values: 1042 continue 1043 row = self.get_row(y, clone=True) 1044 repeated = row.repeated or 1 1045 if repeated >= 2: 1046 row.repeated = None 1047 row.set_values( 1048 row_values, 1049 start=x, 1050 cell_type=cell_type, 1051 currency=currency, 1052 style=style, 1053 ) 1054 self.set_row(y, row, clone=False) 1055 self._update_width(row)
Set the value of cells in the table, from the 'coord' position with values.
'coord' is the coordinate of the upper left cell to be modified by values. If 'coord' is None, default to the position (0,0) ("A1"). If 'coord' is an area (e.g. "A2:B5"), the upper left position of this area is used as coordinate.
The table is not cleared before the operation, to reset the table before setting values, use table.clear().
A list of lists is expected, with as many lists as rows, and as many items in each sublist as cells to be setted. None values in the list will create empty cells with no cell type (but eventually a style).
Arguments:
coord -- tuple or str
values -- list of lists of python types
cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
'string' or 'time'
currency -- three-letter str
style -- str
1057 def rstrip(self, aggressive: bool = False) -> None: 1058 """Remove *in-place* empty rows below and empty cells at the right of 1059 the table. Cells are empty if they contain no value or it evaluates 1060 to False, and no style. 1061 1062 If aggressive is True, empty cells with style are removed too. 1063 1064 Argument: 1065 1066 aggressive -- bool 1067 """ 1068 # Step 1: remove empty rows below the table 1069 for row in reversed(self._get_rows()): 1070 if row.is_empty(aggressive=aggressive): 1071 row.parent.delete(row) # type: ignore 1072 else: 1073 break 1074 # Step 2: rstrip remaining rows 1075 max_width = 0 1076 for row in self._get_rows(): 1077 row.rstrip(aggressive=aggressive) 1078 # keep count of the biggest row 1079 max_width = max(max_width, row.width) 1080 # raz cache of rows 1081 self._indexes["_tmap"] = {} 1082 # Step 3: trim columns to match max_width 1083 columns = self._get_columns() 1084 repeated_cols = self.xpath("table:table-column/@table:number-columns-repeated") 1085 if not isinstance(repeated_cols, list): 1086 raise TypeError 1087 unrepeated = len(columns) - len(repeated_cols) 1088 column_width = sum(int(r) for r in repeated_cols) + unrepeated # type: ignore 1089 diff = column_width - max_width 1090 if diff > 0: 1091 for column in reversed(columns): 1092 repeated = column.repeated or 1 1093 repeated = repeated - diff 1094 if repeated > 0: 1095 column.repeated = repeated 1096 break 1097 else: 1098 column.parent.delete(column) 1099 diff = -repeated 1100 if diff == 0: 1101 break 1102 # raz cache of columns 1103 self._indexes["_cmap"] = {} 1104 self._compute_table_cache()
Remove in-place empty rows below and empty cells at the right of the table. Cells are empty if they contain no value or it evaluates to False, and no style.
If aggressive is True, empty cells with style are removed too.
Argument:
aggressive -- bool
1106 def optimize_width(self) -> None: 1107 """Remove *in-place* empty rows below and empty cells at the right of 1108 the table. Keep repeated styles of empty cells but minimize row width. 1109 """ 1110 self._optimize_width_trim_rows() 1111 width = self._optimize_width_length() 1112 self._optimize_width_rstrip_rows(width) 1113 self._optimize_width_adapt_columns(width)
Remove in-place empty rows below and empty cells at the right of the table. Keep repeated styles of empty cells but minimize row width.
1168 def transpose(self, coord: tuple | list | str | None = None) -> None: # noqa: C901 1169 """Swap *in-place* rows and columns of the table. 1170 1171 If 'coord' is not None, apply transpose only to the area defined by the 1172 coordinates. Beware, if area is not square, some cells mays be over 1173 written during the process. 1174 1175 Arguments: 1176 1177 coord -- str or tuple of int : coordinates of area 1178 1179 start -- int or str 1180 """ 1181 data = [] 1182 if coord is None: 1183 for row in self.traverse(): 1184 data.append(list(row.traverse())) 1185 transposed_data = zip_longest(*data) 1186 self.clear() 1187 # new_rows = [] 1188 for row_cells in transposed_data: 1189 if not isiterable(row_cells): 1190 row_cells = (row_cells,) 1191 row = Row() 1192 row.extend_cells(row_cells) 1193 self.append_row(row, clone=False) 1194 self._compute_table_cache() 1195 else: 1196 x, y, z, t = self._translate_table_coordinates(coord) 1197 if x is None: 1198 x = 0 1199 else: 1200 x = min(x, self.width - 1) 1201 if z is None: 1202 z = self.width - 1 1203 else: 1204 z = min(z, self.width - 1) 1205 if y is None: 1206 y = 0 1207 else: 1208 y = min(y, self.height - 1) 1209 if t is None: 1210 t = self.height - 1 1211 else: 1212 t = min(t, self.height - 1) 1213 for row in self.traverse(start=y, end=t): 1214 data.append(list(row.traverse(start=x, end=z))) 1215 transposed_data = zip_longest(*data) 1216 # clear locally 1217 w = z - x + 1 1218 h = t - y + 1 1219 if w != h: 1220 nones = [[None] * w for i in range(h)] 1221 self.set_values(nones, coord=(x, y, z, t)) 1222 # put transposed 1223 filtered_data: list[tuple[Cell]] = [] 1224 for row_cells in transposed_data: 1225 if isinstance(row_cells, (list, tuple)): 1226 filtered_data.append(row_cells) 1227 else: 1228 filtered_data.append((row_cells,)) 1229 self.set_cells(filtered_data, (x, y, x + h - 1, y + w - 1)) 1230 self._compute_table_cache()
Swap in-place rows and columns of the table.
If 'coord' is not None, apply transpose only to the area defined by the coordinates. Beware, if area is not square, some cells mays be over written during the process.
Arguments:
coord -- str or tuple of int : coordinates of area
start -- int or str
1232 def is_empty(self, aggressive: bool = False) -> bool: 1233 """Return whether every cell in the table has no value or the value 1234 evaluates to False (empty string), and no style. 1235 1236 If aggressive is True, empty cells with style are considered empty. 1237 1238 Arguments: 1239 1240 aggressive -- bool 1241 """ 1242 return all(row.is_empty(aggressive=aggressive) for row in self._get_rows())
Return whether every cell in the table has no value or the value evaluates to False (empty string), and no style.
If aggressive is True, empty cells with style are considered empty.
Arguments:
aggressive -- bool
1251 def traverse( # noqa: C901 1252 self, 1253 start: int | None = None, 1254 end: int | None = None, 1255 ) -> Iterator[Row]: 1256 """Yield as many row elements as expected rows in the table, i.e. 1257 expand repetitions by returning the same row as many times as 1258 necessary. 1259 1260 Arguments: 1261 1262 start -- int 1263 1264 end -- int 1265 1266 Copies are returned, use set_row() to push them back. 1267 """ 1268 idx = -1 1269 before = -1 1270 y = 0 1271 if start is None and end is None: 1272 for juska in self._tmap: 1273 idx += 1 1274 if idx in self._indexes["_tmap"]: 1275 row = self._indexes["_tmap"][idx] 1276 else: 1277 row = self._get_element_idx2(_xpath_row_idx, idx) 1278 self._indexes["_tmap"][idx] = row 1279 repeated = juska - before 1280 before = juska 1281 for _i in range(repeated or 1): 1282 # Return a copy without the now obsolete repetition 1283 row = row.clone 1284 row.y = y 1285 y += 1 1286 if repeated > 1: 1287 row.repeated = None 1288 yield row 1289 else: 1290 if start is None: 1291 start = 0 1292 start = max(0, start) 1293 if end is None: 1294 try: 1295 end = self._tmap[-1] 1296 except Exception: 1297 end = -1 1298 start_map = find_odf_idx(self._tmap, start) 1299 if start_map is None: 1300 return 1301 if start_map > 0: 1302 before = self._tmap[start_map - 1] 1303 idx = start_map - 1 1304 before = start - 1 1305 y = start 1306 for juska in self._tmap[start_map:]: 1307 idx += 1 1308 if idx in self._indexes["_tmap"]: 1309 row = self._indexes["_tmap"][idx] 1310 else: 1311 row = self._get_element_idx2(_xpath_row_idx, idx) 1312 self._indexes["_tmap"][idx] = row 1313 repeated = juska - before 1314 before = juska 1315 for _i in range(repeated or 1): 1316 if y <= end: 1317 row = row.clone 1318 row.y = y 1319 y += 1 1320 if repeated > 1 or (y == start and start > 0): 1321 row.repeated = None 1322 yield row
Yield as many row elements as expected rows in the table, i.e. expand repetitions by returning the same row as many times as necessary.
Arguments:
start -- int
end -- int
Copies are returned, use set_row() to push them back.
1324 def get_rows( 1325 self, 1326 coord: tuple | list | str | None = None, 1327 style: str | None = None, 1328 content: str | None = None, 1329 ) -> list[Row]: 1330 """Get the list of rows matching the criteria. 1331 1332 Filter by coordinates will parse the area defined by the coordinates. 1333 1334 Arguments: 1335 1336 coord -- str or tuple of int : coordinates of rows 1337 1338 content -- str regex 1339 1340 style -- str 1341 1342 Return: list of rows 1343 """ 1344 if coord: 1345 _x, y, _z, t = self._translate_table_coordinates(coord) 1346 else: 1347 y = t = None 1348 # fixme : not clones ? 1349 if not content and not style: 1350 return list(self.traverse(start=y, end=t)) 1351 rows = [] 1352 for row in self.traverse(start=y, end=t): 1353 if content and not row.match(content): 1354 continue 1355 if style and style != row.style: 1356 continue 1357 rows.append(row) 1358 return rows
Get the list of rows matching the criteria.
Filter by coordinates will parse the area defined by the coordinates.
Arguments:
coord -- str or tuple of int : coordinates of rows
content -- str regex
style -- str
Return: list of rows
1383 def get_row(self, y: int | str, clone: bool = True, create: bool = True) -> Row: 1384 """Get the row at the given "y" position. 1385 1386 Position start at 0. So cell A4 is on row 3. 1387 1388 A copy is returned, use set_cell() to push it back. 1389 1390 Arguments: 1391 1392 y -- int or str 1393 1394 Return: Row 1395 """ 1396 # fixme : keep repeat ? maybe an option to functions : "raw=False" 1397 y = self._translate_y_from_any(y) 1398 row = self._get_row2(y, clone=clone, create=create) 1399 if row is None: 1400 raise ValueError("Row not found") 1401 row.y = y 1402 return row
Get the row at the given "y" position.
Position start at 0. So cell A4 is on row 3.
A copy is returned, use set_cell() to push it back.
Arguments:
y -- int or str
Return: Row
1404 def set_row(self, y: int | str, row: Row | None = None, clone: bool = True) -> Row: 1405 """Replace the row at the given position with the new one. Repetions of 1406 the old row will be adjusted. 1407 1408 If row is None, a new empty row is created. 1409 1410 Position start at 0. So cell A4 is on row 3. 1411 1412 Arguments: 1413 1414 y -- int or str 1415 1416 row -- Row 1417 1418 returns the row, with updated row.y 1419 """ 1420 if row is None: 1421 row = Row() 1422 repeated = 1 1423 clone = False 1424 else: 1425 repeated = row.repeated or 1 1426 y = self._translate_y_from_any(y) 1427 row.y = y 1428 # Outside the defined table ? 1429 diff = y - self.height 1430 if diff == 0: 1431 row_back = self.append_row(row, _repeated=repeated, clone=clone) 1432 elif diff > 0: 1433 self.append_row(Row(repeated=diff), _repeated=diff, clone=clone) 1434 row_back = self.append_row(row, _repeated=repeated, clone=clone) 1435 else: 1436 # Inside the defined table 1437 row_back = set_item_in_vault( # type: ignore 1438 y, row, self, _xpath_row_idx, "_tmap", clone=clone 1439 ) 1440 # print self.serialize(True) 1441 # Update width if necessary 1442 self._update_width(row_back) 1443 return row_back
Replace the row at the given position with the new one. Repetions of the old row will be adjusted.
If row is None, a new empty row is created.
Position start at 0. So cell A4 is on row 3.
Arguments:
y -- int or str
row -- Row
returns the row, with updated row.y
1445 def insert_row( 1446 self, y: str | int, row: Row | None = None, clone: bool = True 1447 ) -> Row: 1448 """Insert the row before the given "y" position. If no row is given, 1449 an empty one is created. 1450 1451 Position start at 0. So cell A4 is on row 3. 1452 1453 If row is None, a new empty row is created. 1454 1455 Arguments: 1456 1457 y -- int or str 1458 1459 row -- Row 1460 1461 returns the row, with updated row.y 1462 """ 1463 if row is None: 1464 row = Row() 1465 clone = False 1466 y = self._translate_y_from_any(y) 1467 diff = y - self.height 1468 if diff < 0: 1469 row_back = insert_item_in_vault(y, row, self, _xpath_row_idx, "_tmap") 1470 elif diff == 0: 1471 row_back = self.append_row(row, clone=clone) 1472 else: 1473 self.append_row(Row(repeated=diff), _repeated=diff, clone=False) 1474 row_back = self.append_row(row, clone=clone) 1475 row_back.y = y # type: ignore 1476 # Update width if necessary 1477 self._update_width(row_back) # type: ignore 1478 return row_back # type: ignore
Insert the row before the given "y" position. If no row is given, an empty one is created.
Position start at 0. So cell A4 is on row 3.
If row is None, a new empty row is created.
Arguments:
y -- int or str
row -- Row
returns the row, with updated row.y
1480 def extend_rows(self, rows: list[Row] | None = None) -> None: 1481 """Append a list of rows at the end of the table. 1482 1483 Arguments: 1484 1485 rows -- list of Row 1486 """ 1487 if rows is None: 1488 rows = [] 1489 self.extend(rows) 1490 self._compute_table_cache() 1491 # Update width if necessary 1492 width = self.width 1493 for row in self.traverse(): 1494 if row.width > width: 1495 width = row.width 1496 diff = width - self.width 1497 if diff > 0: 1498 self.append_column(Column(repeated=diff))
Append a list of rows at the end of the table.
Arguments:
rows -- list of Row
1500 def append_row( 1501 self, 1502 row: Row | None = None, 1503 clone: bool = True, 1504 _repeated: int | None = None, 1505 ) -> Row: 1506 """Append the row at the end of the table. If no row is given, an 1507 empty one is created. 1508 1509 Position start at 0. So cell A4 is on row 3. 1510 1511 Note the columns are automatically created when the first row is 1512 inserted in an empty table. So better insert a filled row. 1513 1514 Arguments: 1515 1516 row -- Row 1517 1518 _repeated -- (optional), repeated value of the row 1519 1520 returns the row, with updated row.y 1521 """ 1522 if row is None: 1523 row = Row() 1524 _repeated = 1 1525 elif clone: 1526 row = row.clone 1527 # Appending a repeated row accepted 1528 # Do not insert next to the last row because it could be in a group 1529 self._append(row) 1530 if _repeated is None: 1531 _repeated = row.repeated or 1 1532 self._tmap = insert_map_once(self._tmap, len(self._tmap), _repeated) 1533 row.y = self.height - 1 1534 # Initialize columns 1535 if not self._get_columns(): 1536 repeated = row.width 1537 self.insert(Column(repeated=repeated), position=0) 1538 self._compute_table_cache() 1539 # Update width if necessary 1540 self._update_width(row) 1541 return row
Append the row at the end of the table. If no row is given, an empty one is created.
Position start at 0. So cell A4 is on row 3.
Note the columns are automatically created when the first row is inserted in an empty table. So better insert a filled row.
Arguments:
row -- Row
_repeated -- (optional), repeated value of the row
returns the row, with updated row.y
1543 def delete_row(self, y: int | str) -> None: 1544 """Delete the row at the given "y" position. 1545 1546 Position start at 0. So cell A4 is on row 3. 1547 1548 Arguments: 1549 1550 y -- int or str 1551 """ 1552 y = self._translate_y_from_any(y) 1553 # Outside the defined table 1554 if y >= self.height: 1555 return 1556 # Inside the defined table 1557 delete_item_in_vault(y, self, _xpath_row_idx, "_tmap")
Delete the row at the given "y" position.
Position start at 0. So cell A4 is on row 3.
Arguments:
y -- int or str
1559 def get_row_values( 1560 self, 1561 y: int | str, 1562 cell_type: str | None = None, 1563 complete: bool = True, 1564 get_type: bool = False, 1565 ) -> list: 1566 """Shortcut to get the list of Python values for the cells of the row 1567 at the given "y" position. 1568 1569 Position start at 0. So cell A4 is on row 3. 1570 1571 Filter by cell_type, with cell_type 'all' will retrieve cells of any 1572 type, aka non empty cells. 1573 If cell_type and complete is True, replace missing values by None. 1574 1575 If get_type is True, returns a tuple (value, ODF type of value) 1576 1577 Arguments: 1578 1579 y -- int, str 1580 1581 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 1582 'currency', 'percentage' or 'all' 1583 1584 complete -- boolean 1585 1586 get_type -- boolean 1587 1588 Return: list of lists of Python types 1589 """ 1590 values = self.get_row(y, clone=False).get_values( 1591 cell_type=cell_type, complete=complete, get_type=get_type 1592 ) 1593 # complete row to match column width 1594 if complete: 1595 if get_type: 1596 values.extend([(None, None)] * (self.width - len(values))) 1597 else: 1598 values.extend([None] * (self.width - len(values))) 1599 return values
Shortcut to get the list of Python values for the cells of the row at the given "y" position.
Position start at 0. So cell A4 is on row 3.
Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells. If cell_type and complete is True, replace missing values by None.
If get_type is True, returns a tuple (value, ODF type of value)
Arguments:
y -- int, str
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency', 'percentage' or 'all'
complete -- boolean
get_type -- boolean
Return: list of lists of Python types
1601 def set_row_values( 1602 self, 1603 y: int | str, 1604 values: list, 1605 cell_type: str | None = None, 1606 currency: str | None = None, 1607 style: str | None = None, 1608 ) -> Row: 1609 """Shortcut to set the values of *all* cells of the row at the given 1610 "y" position. 1611 1612 Position start at 0. So cell A4 is on row 3. 1613 1614 Arguments: 1615 1616 y -- int or str 1617 1618 values -- list of Python types 1619 1620 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 1621 'string' or 'time' 1622 1623 currency -- three-letter str 1624 1625 style -- str 1626 1627 returns the row, with updated row.y 1628 """ 1629 row = Row() # needed if clones rows 1630 row.set_values(values, style=style, cell_type=cell_type, currency=currency) 1631 return self.set_row(y, row) # needed if clones rows
Shortcut to set the values of all cells of the row at the given "y" position.
Position start at 0. So cell A4 is on row 3.
Arguments:
y -- int or str
values -- list of Python types
cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
'string' or 'time'
currency -- three-letter str
style -- str
returns the row, with updated row.y
1633 def set_row_cells(self, y: int | str, cells: list | None = None) -> Row: 1634 """Shortcut to set *all* the cells of the row at the given 1635 "y" position. 1636 1637 Position start at 0. So cell A4 is on row 3. 1638 1639 Arguments: 1640 1641 y -- int or str 1642 1643 cells -- list of Python types 1644 1645 style -- str 1646 1647 returns the row, with updated row.y 1648 """ 1649 if cells is None: 1650 cells = [] 1651 row = Row() # needed if clones rows 1652 row.extend_cells(cells) 1653 return self.set_row(y, row) # needed if clones rows
Shortcut to set all the cells of the row at the given "y" position.
Position start at 0. So cell A4 is on row 3.
Arguments:
y -- int or str
cells -- list of Python types
style -- str
returns the row, with updated row.y
1655 def is_row_empty(self, y: int | str, aggressive: bool = False) -> bool: 1656 """Return wether every cell in the row at the given "y" position has 1657 no value or the value evaluates to False (empty string), and no style. 1658 1659 Position start at 0. So cell A4 is on row 3. 1660 1661 If aggressive is True, empty cells with style are considered empty. 1662 1663 Arguments: 1664 1665 y -- int or str 1666 1667 aggressive -- bool 1668 """ 1669 return self.get_row(y, clone=False).is_empty(aggressive=aggressive)
Return wether every cell in the row at the given "y" position has no value or the value evaluates to False (empty string), and no style.
Position start at 0. So cell A4 is on row 3.
If aggressive is True, empty cells with style are considered empty.
Arguments:
y -- int or str
aggressive -- bool
1675 def get_cells( 1676 self, 1677 coord: tuple | list | str | None = None, 1678 cell_type: str | None = None, 1679 style: str | None = None, 1680 content: str | None = None, 1681 flat: bool = False, 1682 ) -> list: 1683 """Get the cells matching the criteria. If 'coord' is None, 1684 parse the whole table, else parse the area defined by 'coord'. 1685 1686 Filter by cell_type = "all" will retrieve cells of any 1687 type, aka non empty cells. 1688 1689 If flat is True (default is False), the method return a single list 1690 of all the values, else a list of lists of cells. 1691 1692 if cell_type, style and content are None, get_cells() will return 1693 the exact number of cells of the area, including empty cells. 1694 1695 Arguments: 1696 1697 coordinates -- str or tuple of int : coordinates of area 1698 1699 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 1700 'currency', 'percentage' or 'all' 1701 1702 content -- str regex 1703 1704 style -- str 1705 1706 flat -- boolean 1707 1708 Return: list of tuples 1709 """ 1710 if coord: 1711 x, y, z, t = self._translate_table_coordinates(coord) 1712 else: 1713 x = y = z = t = None 1714 if flat: 1715 cells: list[Cell] = [] 1716 for row in self.traverse(start=y, end=t): 1717 row_cells = row.get_cells( 1718 coord=(x, z), 1719 cell_type=cell_type, 1720 style=style, 1721 content=content, 1722 ) 1723 cells.extend(row_cells) 1724 return cells 1725 else: 1726 lcells: list[list[Cell]] = [] 1727 for row in self.traverse(start=y, end=t): 1728 row_cells = row.get_cells( 1729 coord=(x, z), 1730 cell_type=cell_type, 1731 style=style, 1732 content=content, 1733 ) 1734 lcells.append(row_cells) 1735 return lcells
Get the cells matching the criteria. If 'coord' is None, parse the whole table, else parse the area defined by 'coord'.
Filter by cell_type = "all" will retrieve cells of any type, aka non empty cells.
If flat is True (default is False), the method return a single list of all the values, else a list of lists of cells.
if cell_type, style and content are None, get_cells() will return the exact number of cells of the area, including empty cells.
Arguments:
coordinates -- str or tuple of int : coordinates of area
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency', 'percentage' or 'all'
content -- str regex
style -- str
flat -- boolean
Return: list of tuples
1737 def get_cell( 1738 self, 1739 coord: tuple | list | str, 1740 clone: bool = True, 1741 keep_repeated: bool = True, 1742 ) -> Cell: 1743 """Get the cell at the given coordinates. 1744 1745 They are either a 2-uplet of (x, y) starting from 0, or a 1746 human-readable position like "C4". 1747 1748 A copy is returned, use ``set_cell`` to push it back. 1749 1750 Arguments: 1751 1752 coord -- (int, int) or str 1753 1754 Return: Cell 1755 """ 1756 x, y = self._translate_cell_coordinates(coord) 1757 if x is None: 1758 raise ValueError 1759 if y is None: 1760 raise ValueError 1761 # Outside the defined table 1762 if y >= self.height: 1763 cell = Cell() 1764 else: 1765 # Inside the defined table 1766 row = self._get_row2_base(y) 1767 if row is None: 1768 raise ValueError 1769 read_cell = row.get_cell(x, clone=clone) 1770 if read_cell is None: 1771 raise ValueError 1772 cell = read_cell 1773 if not keep_repeated: 1774 repeated = cell.repeated or 1 1775 if repeated >= 2: 1776 cell.repeated = None 1777 cell.x = x 1778 cell.y = y 1779 return cell
Get the cell at the given coordinates.
They are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".
A copy is returned, use set_cell to push it back.
Arguments:
coord -- (int, int) or str
Return: Cell
1781 def get_value( 1782 self, 1783 coord: tuple | list | str, 1784 get_type: bool = False, 1785 ) -> Any: 1786 """Shortcut to get the Python value of the cell at the given 1787 coordinates. 1788 1789 If get_type is True, returns the tuples (value, ODF type) 1790 1791 coord is either a 2-uplet of (x, y) starting from 0, or a 1792 human-readable position like "C4". If an Area is given, the upper 1793 left position is used as coord. 1794 1795 Arguments: 1796 1797 coord -- (int, int) or str : coordinate 1798 1799 Return: Python type 1800 """ 1801 x, y = self._translate_cell_coordinates(coord) 1802 if x is None: 1803 raise ValueError 1804 if y is None: 1805 raise ValueError 1806 # Outside the defined table 1807 if y >= self.height: 1808 if get_type: 1809 return (None, None) 1810 return None 1811 else: 1812 # Inside the defined table 1813 row = self._get_row2_base(y) 1814 if row is None: 1815 raise ValueError 1816 cell = row._get_cell2_base(x) 1817 if cell is None: 1818 if get_type: 1819 return (None, None) 1820 return None 1821 return cell.get_value(get_type=get_type)
Shortcut to get the Python value of the cell at the given coordinates.
If get_type is True, returns the tuples (value, ODF type)
coord is either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4". If an Area is given, the upper left position is used as coord.
Arguments:
coord -- (int, int) or str : coordinate
Return: Python type
1823 def set_cell( 1824 self, 1825 coord: tuple | list | str, 1826 cell: Cell | None = None, 1827 clone: bool = True, 1828 ) -> Cell: 1829 """Replace a cell of the table at the given coordinates. 1830 1831 They are either a 2-uplet of (x, y) starting from 0, or a 1832 human-readable position like "C4". 1833 1834 Arguments: 1835 1836 coord -- (int, int) or str : coordinate 1837 1838 cell -- Cell 1839 1840 return the cell, with x and y updated 1841 """ 1842 if cell is None: 1843 cell = Cell() 1844 clone = False 1845 x, y = self._translate_cell_coordinates(coord) 1846 if x is None: 1847 raise ValueError 1848 if y is None: 1849 raise ValueError 1850 cell.x = x 1851 cell.y = y 1852 if y >= self.height: 1853 row = Row() 1854 cell_back = row.set_cell(x, cell, clone=clone) 1855 self.set_row(y, row, clone=False) 1856 else: 1857 row_read = self._get_row2_base(y) 1858 if row_read is None: 1859 raise ValueError 1860 row = row_read 1861 row.y = y 1862 repeated = row.repeated or 1 1863 if repeated > 1: 1864 row = row.clone 1865 row.repeated = None 1866 cell_back = row.set_cell(x, cell, clone=clone) 1867 self.set_row(y, row, clone=False) 1868 else: 1869 cell_back = row.set_cell(x, cell, clone=clone) 1870 # Update width if necessary, since we don't use set_row 1871 self._update_width(row) 1872 return cell_back
Replace a cell of the table at the given coordinates.
They are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".
Arguments:
coord -- (int, int) or str : coordinate
cell -- Cell
return the cell, with x and y updated
1874 def set_cells( 1875 self, 1876 cells: list[list[Cell]] | list[tuple[Cell]], 1877 coord: tuple | list | str | None = None, 1878 clone: bool = True, 1879 ) -> None: 1880 """Set the cells in the table, from the 'coord' position. 1881 1882 'coord' is the coordinate of the upper left cell to be modified by 1883 values. If 'coord' is None, default to the position (0,0) ("A1"). 1884 If 'coord' is an area (e.g. "A2:B5"), the upper left position of this 1885 area is used as coordinate. 1886 1887 The table is *not* cleared before the operation, to reset the table 1888 before setting cells, use table.clear(). 1889 1890 A list of lists is expected, with as many lists as rows to be set, and 1891 as many cell in each sublist as cells to be setted in the row. 1892 1893 Arguments: 1894 1895 cells -- list of list of cells 1896 1897 coord -- tuple or str 1898 1899 values -- list of lists of python types 1900 """ 1901 if coord: 1902 x, y = self._translate_cell_coordinates(coord) 1903 else: 1904 x = y = 0 1905 if y is None: 1906 y = 0 1907 if x is None: 1908 x = 0 1909 y -= 1 1910 for row_cells in cells: 1911 y += 1 1912 if not row_cells: 1913 continue 1914 row = self.get_row(y, clone=True) 1915 repeated = row.repeated or 1 1916 if repeated >= 2: 1917 row.repeated = None 1918 row.set_cells(row_cells, start=x, clone=clone) 1919 self.set_row(y, row, clone=False) 1920 self._update_width(row)
Set the cells in the table, from the 'coord' position.
'coord' is the coordinate of the upper left cell to be modified by values. If 'coord' is None, default to the position (0,0) ("A1"). If 'coord' is an area (e.g. "A2:B5"), the upper left position of this area is used as coordinate.
The table is not cleared before the operation, to reset the table before setting cells, use table.clear().
A list of lists is expected, with as many lists as rows to be set, and as many cell in each sublist as cells to be setted in the row.
Arguments:
cells -- list of list of cells
coord -- tuple or str
values -- list of lists of python types
1922 def set_value( 1923 self, 1924 coord: tuple | list | str, 1925 value: Any, 1926 cell_type: str | None = None, 1927 currency: str | None = None, 1928 style: str | None = None, 1929 ) -> None: 1930 """Set the Python value of the cell at the given coordinates. 1931 1932 They are either a 2-uplet of (x, y) starting from 0, or a 1933 human-readable position like "C4". 1934 1935 Arguments: 1936 1937 coord -- (int, int) or str 1938 1939 value -- Python type 1940 1941 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 1942 'string' or 'time' 1943 1944 currency -- three-letter str 1945 1946 style -- str 1947 1948 """ 1949 self.set_cell( 1950 coord, 1951 Cell(value, cell_type=cell_type, currency=currency, style=style), 1952 clone=False, 1953 )
Set the Python value of the cell at the given coordinates.
They are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".
Arguments:
coord -- (int, int) or str
value -- Python type
cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
'string' or 'time'
currency -- three-letter str
style -- str
1955 def set_cell_image( 1956 self, 1957 coord: tuple | list | str, 1958 image_frame: Frame, 1959 doc_type: str | None = None, 1960 ) -> None: 1961 """Do all the magic to display an image in the cell at the given 1962 coordinates. 1963 1964 They are either a 2-uplet of (x, y) starting from 0, or a 1965 human-readable position like "C4". 1966 1967 The frame element must contain the expected image position and 1968 dimensions. 1969 1970 DrawImage insertion depends on the document type, so the type must be 1971 provided or the table element must be already attached to a document. 1972 1973 Arguments: 1974 1975 coord -- (int, int) or str 1976 1977 image_frame -- Frame including an image 1978 1979 doc_type -- 'spreadsheet' or 'text' 1980 """ 1981 # Test document type 1982 if doc_type is None: 1983 body = self.document_body 1984 if body is None: 1985 raise ValueError("document type not found") 1986 doc_type = {"office:spreadsheet": "spreadsheet", "office:text": "text"}.get( 1987 body.tag 1988 ) 1989 if doc_type is None: 1990 raise ValueError("document type not supported for images") 1991 # We need the end address of the image 1992 x, y = self._translate_cell_coordinates(coord) 1993 if x is None: 1994 raise ValueError 1995 if y is None: 1996 raise ValueError 1997 cell = self.get_cell((x, y)) 1998 image_frame = image_frame.clone # type: ignore 1999 # Remove any previous paragraph, frame, etc. 2000 for child in cell.children: 2001 cell.delete(child) 2002 # Now it all depends on the document type 2003 if doc_type == "spreadsheet": 2004 image_frame.anchor_type = "char" 2005 # The frame needs end coordinates 2006 width, height = image_frame.size 2007 image_frame.set_attribute("table:end-x", width) 2008 image_frame.set_attribute("table:end-y", height) 2009 # FIXME what happens when the address changes? 2010 address = f"{self.name}.{digit_to_alpha(x)}{y + 1}" 2011 image_frame.set_attribute("table:end-cell-address", address) 2012 # The frame is directly in the cell 2013 cell.append(image_frame) 2014 elif doc_type == "text": 2015 # The frame must be in a paragraph 2016 cell.set_value("") 2017 paragraph = cell.get_element("text:p") 2018 if paragraph is None: 2019 raise ValueError 2020 paragraph.append(image_frame) 2021 self.set_cell(coord, cell)
Do all the magic to display an image in the cell at the given coordinates.
They are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".
The frame element must contain the expected image position and dimensions.
DrawImage insertion depends on the document type, so the type must be provided or the table element must be already attached to a document.
Arguments:
coord -- (int, int) or str
image_frame -- Frame including an image
doc_type -- 'spreadsheet' or 'text'
2023 def insert_cell( 2024 self, 2025 coord: tuple | list | str, 2026 cell: Cell | None = None, 2027 clone: bool = True, 2028 ) -> Cell: 2029 """Insert the given cell at the given coordinates. If no cell is 2030 given, an empty one is created. 2031 2032 Coordinates are either a 2-uplet of (x, y) starting from 0, or a 2033 human-readable position like "C4". 2034 2035 Cells on the right are shifted. Other rows remain untouched. 2036 2037 Arguments: 2038 2039 coord -- (int, int) or str 2040 2041 cell -- Cell 2042 2043 returns the cell with x and y updated 2044 """ 2045 if cell is None: 2046 cell = Cell() 2047 clone = False 2048 if clone: 2049 cell = cell.clone 2050 x, y = self._translate_cell_coordinates(coord) 2051 if x is None: 2052 raise ValueError 2053 if y is None: 2054 raise ValueError 2055 row = self._get_row2(y, clone=True) 2056 row.y = y 2057 row.repeated = None 2058 cell_back = row.insert_cell(x, cell, clone=False) 2059 self.set_row(y, row, clone=False) 2060 # Update width if necessary 2061 self._update_width(row) 2062 return cell_back
Insert the given cell at the given coordinates. If no cell is given, an empty one is created.
Coordinates are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".
Cells on the right are shifted. Other rows remain untouched.
Arguments:
coord -- (int, int) or str
cell -- Cell
returns the cell with x and y updated
2064 def append_cell( 2065 self, 2066 y: int | str, 2067 cell: Cell | None = None, 2068 clone: bool = True, 2069 ) -> Cell: 2070 """Append the given cell at the "y" coordinate. Repeated cells are 2071 accepted. If no cell is given, an empty one is created. 2072 2073 Position start at 0. So cell A4 is on row 3. 2074 2075 Other rows remain untouched. 2076 2077 Arguments: 2078 2079 y -- int or str 2080 2081 cell -- Cell 2082 2083 returns the cell with x and y updated 2084 """ 2085 if cell is None: 2086 cell = Cell() 2087 clone = False 2088 if clone: 2089 cell = cell.clone 2090 y = self._translate_y_from_any(y) 2091 row = self._get_row2(y) 2092 row.y = y 2093 cell_back = row.append_cell(cell, clone=False) 2094 self.set_row(y, row) 2095 # Update width if necessary 2096 self._update_width(row) 2097 return cell_back
Append the given cell at the "y" coordinate. Repeated cells are accepted. If no cell is given, an empty one is created.
Position start at 0. So cell A4 is on row 3.
Other rows remain untouched.
Arguments:
y -- int or str
cell -- Cell
returns the cell with x and y updated
2099 def delete_cell(self, coord: tuple | list | str) -> None: 2100 """Delete the cell at the given coordinates, so that next cells are 2101 shifted to the left. 2102 2103 Coordinates are either a 2-uplet of (x, y) starting from 0, or a 2104 human-readable position like "C4". 2105 2106 Use set_value() for erasing value. 2107 2108 Arguments: 2109 2110 coord -- (int, int) or str 2111 """ 2112 x, y = self._translate_cell_coordinates(coord) 2113 if x is None: 2114 raise ValueError 2115 if y is None: 2116 raise ValueError 2117 # Outside the defined table 2118 if y >= self.height: 2119 return 2120 # Inside the defined table 2121 row = self._get_row2_base(y) 2122 if row is None: 2123 raise ValueError 2124 row.delete_cell(x) 2125 # self.set_row(y, row)
Delete the cell at the given coordinates, so that next cells are shifted to the left.
Coordinates are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".
Use set_value() for erasing value.
Arguments:
coord -- (int, int) or str
2132 def traverse_columns( # noqa: C901 2133 self, 2134 start: int | None = None, 2135 end: int | None = None, 2136 ) -> Iterator[Column]: 2137 """Yield as many column elements as expected columns in the table, 2138 i.e. expand repetitions by returning the same column as many times as 2139 necessary. 2140 2141 Arguments: 2142 2143 start -- int 2144 2145 end -- int 2146 2147 Copies are returned, use set_column() to push them back. 2148 """ 2149 idx = -1 2150 before = -1 2151 x = 0 2152 if start is None and end is None: 2153 for juska in self._cmap: 2154 idx += 1 2155 if idx in self._indexes["_cmap"]: 2156 column = self._indexes["_cmap"][idx] 2157 else: 2158 column = self._get_element_idx2(_xpath_column_idx, idx) 2159 self._indexes["_cmap"][idx] = column 2160 repeated = juska - before 2161 before = juska 2162 for _i in range(repeated or 1): 2163 # Return a copy without the now obsolete repetition 2164 column = column.clone 2165 column.x = x 2166 x += 1 2167 if repeated > 1: 2168 column.repeated = None 2169 yield column 2170 else: 2171 if start is None: 2172 start = 0 2173 start = max(0, start) 2174 if end is None: 2175 try: 2176 end = self._cmap[-1] 2177 except Exception: 2178 end = -1 2179 start_map = find_odf_idx(self._cmap, start) 2180 if start_map is None: 2181 return 2182 if start_map > 0: 2183 before = self._cmap[start_map - 1] 2184 idx = start_map - 1 2185 before = start - 1 2186 x = start 2187 for juska in self._cmap[start_map:]: 2188 idx += 1 2189 if idx in self._indexes["_cmap"]: 2190 column = self._indexes["_cmap"][idx] 2191 else: 2192 column = self._get_element_idx2(_xpath_column_idx, idx) 2193 self._indexes["_cmap"][idx] = column 2194 repeated = juska - before 2195 before = juska 2196 for _i in range(repeated or 1): 2197 if x <= end: 2198 column = column.clone 2199 column.x = x 2200 x += 1 2201 if repeated > 1 or (x == start and start > 0): 2202 column.repeated = None 2203 yield column
Yield as many column elements as expected columns in the table, i.e. expand repetitions by returning the same column as many times as necessary.
Arguments:
start -- int
end -- int
Copies are returned, use set_column() to push them back.
2205 def get_columns( 2206 self, 2207 coord: tuple | list | str | None = None, 2208 style: str | None = None, 2209 ) -> list[Column]: 2210 """Get the list of columns matching the criteria. Each result is a 2211 tuple of (x, column). 2212 2213 Arguments: 2214 2215 coord -- str or tuple of int : coordinates of columns 2216 2217 style -- str 2218 2219 Return: list of columns 2220 """ 2221 if coord: 2222 x, _y, _z, t = self._translate_column_coordinates(coord) 2223 else: 2224 x = t = None 2225 if not style: 2226 return list(self.traverse_columns(start=x, end=t)) 2227 columns = [] 2228 for column in self.traverse_columns(start=x, end=t): 2229 if style != column.style: 2230 continue 2231 columns.append(column) 2232 return columns
Get the list of columns matching the criteria. Each result is a tuple of (x, column).
Arguments:
coord -- str or tuple of int : coordinates of columns
style -- str
Return: list of columns
2249 def get_column(self, x: int | str) -> Column: 2250 """Get the column at the given "x" position. 2251 2252 ODF columns don't contain cells, only style information. 2253 2254 Position start at 0. So cell C4 is on column 2. Alphabetical position 2255 like "C" is accepted. 2256 2257 A copy is returned, use set_column() to push it back. 2258 2259 Arguments: 2260 2261 x -- int or str 2262 2263 Return: Column 2264 """ 2265 x = self._translate_x_from_any(x) 2266 column = self._get_column2(x) 2267 if column is None: 2268 raise ValueError 2269 column.x = x 2270 return column
Get the column at the given "x" position.
ODF columns don't contain cells, only style information.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
A copy is returned, use set_column() to push it back.
Arguments:
x -- int or str
Return: Column
2272 def set_column( 2273 self, 2274 x: int | str, 2275 column: Column | None = None, 2276 ) -> Column: 2277 """Replace the column at the given "x" position. 2278 2279 ODF columns don't contain cells, only style information. 2280 2281 Position start at 0. So cell C4 is on column 2. Alphabetical position 2282 like "C" is accepted. 2283 2284 Arguments: 2285 2286 x -- int or str 2287 2288 column -- Column 2289 """ 2290 x = self._translate_x_from_any(x) 2291 if column is None: 2292 column = Column() 2293 repeated = 1 2294 else: 2295 repeated = column.repeated or 1 2296 column.x = x 2297 # Outside the defined table ? 2298 diff = x - self.width 2299 if diff == 0: 2300 column_back = self.append_column(column, _repeated=repeated) 2301 elif diff > 0: 2302 self.append_column(Column(repeated=diff), _repeated=diff) 2303 column_back = self.append_column(column, _repeated=repeated) 2304 else: 2305 # Inside the defined table 2306 column_back = set_item_in_vault( # type: ignore 2307 x, column, self, _xpath_column_idx, "_cmap" 2308 ) 2309 return column_back
Replace the column at the given "x" position.
ODF columns don't contain cells, only style information.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
Arguments:
x -- int or str
column -- Column
2311 def insert_column( 2312 self, 2313 x: int | str, 2314 column: Column | None = None, 2315 ) -> Column: 2316 """Insert the column before the given "x" position. If no column is 2317 given, an empty one is created. 2318 2319 ODF columns don't contain cells, only style information. 2320 2321 Position start at 0. So cell C4 is on column 2. Alphabetical position 2322 like "C" is accepted. 2323 2324 Arguments: 2325 2326 x -- int or str 2327 2328 column -- Column 2329 """ 2330 if column is None: 2331 column = Column() 2332 x = self._translate_x_from_any(x) 2333 diff = x - self.width 2334 if diff < 0: 2335 column_back = insert_item_in_vault( 2336 x, column, self, _xpath_column_idx, "_cmap" 2337 ) 2338 elif diff == 0: 2339 column_back = self.append_column(column.clone) 2340 else: 2341 self.append_column(Column(repeated=diff), _repeated=diff) 2342 column_back = self.append_column(column.clone) 2343 column_back.x = x # type: ignore 2344 # Repetitions are accepted 2345 repeated = column.repeated or 1 2346 # Update width on every row 2347 for row in self._get_rows(): 2348 if row.width > x: 2349 row.insert_cell(x, Cell(repeated=repeated)) 2350 # Shorter rows don't need insert 2351 # Longer rows shouldn't exist! 2352 return column_back # type: ignore
Insert the column before the given "x" position. If no column is given, an empty one is created.
ODF columns don't contain cells, only style information.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
Arguments:
x -- int or str
column -- Column
2354 def append_column( 2355 self, 2356 column: Column | None = None, 2357 _repeated: int | None = None, 2358 ) -> Column: 2359 """Append the column at the end of the table. If no column is given, 2360 an empty one is created. 2361 2362 ODF columns don't contain cells, only style information. 2363 2364 Position start at 0. So cell C4 is on column 2. Alphabetical position 2365 like "C" is accepted. 2366 2367 Arguments: 2368 2369 column -- Column 2370 """ 2371 if column is None: 2372 column = Column() 2373 else: 2374 column = column.clone 2375 if not self._cmap: 2376 position = 0 2377 else: 2378 odf_idx = len(self._cmap) - 1 2379 last_column = self._get_element_idx2(_xpath_column_idx, odf_idx) 2380 if last_column is None: 2381 raise ValueError 2382 position = self.index(last_column) + 1 2383 column.x = self.width 2384 self.insert(column, position=position) 2385 # Repetitions are accepted 2386 if _repeated is None: 2387 _repeated = column.repeated or 1 2388 self._cmap = insert_map_once(self._cmap, len(self._cmap), _repeated) 2389 # No need to update row widths 2390 return column
Append the column at the end of the table. If no column is given, an empty one is created.
ODF columns don't contain cells, only style information.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
Arguments:
column -- Column
2392 def delete_column(self, x: int | str) -> None: 2393 """Delete the column at the given position. ODF columns don't contain 2394 cells, only style information. 2395 2396 Position start at 0. So cell C4 is on column 2. Alphabetical position 2397 like "C" is accepted. 2398 2399 Arguments: 2400 2401 x -- int or str 2402 """ 2403 x = self._translate_x_from_any(x) 2404 # Outside the defined table 2405 if x >= self.width: 2406 return 2407 # Inside the defined table 2408 delete_item_in_vault(x, self, _xpath_column_idx, "_cmap") 2409 # Update width 2410 width = self.width 2411 for row in self._get_rows(): 2412 if row.width >= width: 2413 row.delete_cell(x)
Delete the column at the given position. ODF columns don't contain cells, only style information.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
Arguments:
x -- int or str
2415 def get_column_cells( # noqa: C901 2416 self, 2417 x: int | str, 2418 style: str | None = None, 2419 content: str | None = None, 2420 cell_type: str | None = None, 2421 complete: bool = False, 2422 ) -> list[Cell | None]: 2423 """Get the list of cells at the given position. 2424 2425 Position start at 0. So cell C4 is on column 2. Alphabetical position 2426 like "C" is accepted. 2427 2428 Filter by cell_type, with cell_type 'all' will retrieve cells of any 2429 type, aka non empty cells. 2430 2431 If complete is True, replace missing values by None. 2432 2433 Arguments: 2434 2435 x -- int or str 2436 2437 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 2438 'currency', 'percentage' or 'all' 2439 2440 content -- str regex 2441 2442 style -- str 2443 2444 complete -- boolean 2445 2446 Return: list of Cell 2447 """ 2448 x = self._translate_x_from_any(x) 2449 if cell_type: 2450 cell_type = cell_type.lower().strip() 2451 cells: list[Cell | None] = [] 2452 if not style and not content and not cell_type: 2453 for row in self.traverse(): 2454 cells.append(row.get_cell(x, clone=True)) 2455 return cells 2456 for row in self.traverse(): 2457 cell = row.get_cell(x, clone=True) 2458 if cell is None: 2459 raise ValueError 2460 # Filter the cells by cell_type 2461 if cell_type: 2462 ctype = cell.type 2463 if not ctype or not (ctype == cell_type or cell_type == "all"): 2464 if complete: 2465 cells.append(None) 2466 continue 2467 # Filter the cells with the regex 2468 if content and not cell.match(content): 2469 if complete: 2470 cells.append(None) 2471 continue 2472 # Filter the cells with the style 2473 if style and style != cell.style: 2474 if complete: 2475 cells.append(None) 2476 continue 2477 cells.append(cell) 2478 return cells
Get the list of cells at the given position.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells.
If complete is True, replace missing values by None.
Arguments:
x -- int or str
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency', 'percentage' or 'all'
content -- str regex
style -- str
complete -- boolean
Return: list of Cell
2480 def get_column_values( 2481 self, 2482 x: int | str, 2483 cell_type: str | None = None, 2484 complete: bool = True, 2485 get_type: bool = False, 2486 ) -> list[Any]: 2487 """Shortcut to get the list of Python values for the cells at the 2488 given position. 2489 2490 Position start at 0. So cell C4 is on column 2. Alphabetical position 2491 like "C" is accepted. 2492 2493 Filter by cell_type, with cell_type 'all' will retrieve cells of any 2494 type, aka non empty cells. 2495 If cell_type and complete is True, replace missing values by None. 2496 2497 If get_type is True, returns a tuple (value, ODF type of value) 2498 2499 Arguments: 2500 2501 x -- int or str 2502 2503 cell_type -- 'boolean', 'float', 'date', 'string', 'time', 2504 'currency', 'percentage' or 'all' 2505 2506 complete -- boolean 2507 2508 get_type -- boolean 2509 2510 Return: list of Python types 2511 """ 2512 cells = self.get_column_cells( 2513 x, style=None, content=None, cell_type=cell_type, complete=complete 2514 ) 2515 values: list[Any] = [] 2516 for cell in cells: 2517 if cell is None: 2518 if complete: 2519 if get_type: 2520 values.append((None, None)) 2521 else: 2522 values.append(None) 2523 continue 2524 if cell_type: 2525 ctype = cell.type 2526 if not ctype or not (ctype == cell_type or cell_type == "all"): 2527 if complete: 2528 if get_type: 2529 values.append((None, None)) 2530 else: 2531 values.append(None) 2532 continue 2533 values.append(cell.get_value(get_type=get_type)) 2534 return values
Shortcut to get the list of Python values for the cells at the given position.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells. If cell_type and complete is True, replace missing values by None.
If get_type is True, returns a tuple (value, ODF type of value)
Arguments:
x -- int or str
cell_type -- 'boolean', 'float', 'date', 'string', 'time',
'currency', 'percentage' or 'all'
complete -- boolean
get_type -- boolean
Return: list of Python types
2536 def set_column_cells(self, x: int | str, cells: list[Cell]) -> None: 2537 """Shortcut to set the list of cells at the given position. 2538 2539 Position start at 0. So cell C4 is on column 2. Alphabetical position 2540 like "C" is accepted. 2541 2542 The list must have the same length than the table height. 2543 2544 Arguments: 2545 2546 x -- int or str 2547 2548 cells -- list of Cell 2549 """ 2550 height = self.height 2551 if len(cells) != height: 2552 raise ValueError(f"col mismatch: {height} cells expected") 2553 cells_iterator = iter(cells) 2554 for y, row in enumerate(self.traverse()): 2555 row.set_cell(x, next(cells_iterator)) 2556 self.set_row(y, row)
Shortcut to set the list of cells at the given position.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
The list must have the same length than the table height.
Arguments:
x -- int or str
cells -- list of Cell
2558 def set_column_values( 2559 self, 2560 x: int | str, 2561 values: list, 2562 cell_type: str | None = None, 2563 currency: str | None = None, 2564 style: str | None = None, 2565 ) -> None: 2566 """Shortcut to set the list of Python values of cells at the given 2567 position. 2568 2569 Position start at 0. So cell C4 is on column 2. Alphabetical position 2570 like "C" is accepted. 2571 2572 The list must have the same length than the table height. 2573 2574 Arguments: 2575 2576 x -- int or str 2577 2578 values -- list of Python types 2579 2580 cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage', 2581 'string' or 'time' 2582 2583 currency -- three-letter str 2584 2585 style -- str 2586 """ 2587 cells = [ 2588 Cell(value, cell_type=cell_type, currency=currency, style=style) 2589 for value in values 2590 ] 2591 self.set_column_cells(x, cells)
Shortcut to set the list of Python values of cells at the given position.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
The list must have the same length than the table height.
Arguments:
x -- int or str
values -- list of Python types
cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
'string' or 'time'
currency -- three-letter str
style -- str
2593 def is_column_empty(self, x: int | str, aggressive: bool = False) -> bool: 2594 """Return wether every cell in the column at "x" position has no value 2595 or the value evaluates to False (empty string), and no style. 2596 2597 Position start at 0. So cell C4 is on column 2. Alphabetical position 2598 like "C" is accepted. 2599 2600 If aggressive is True, empty cells with style are considered empty. 2601 2602 Return: bool 2603 """ 2604 for cell in self.get_column_cells(x): 2605 if cell is None: 2606 continue 2607 if not cell.is_empty(aggressive=aggressive): 2608 return False 2609 return True
Return wether every cell in the column at "x" position has no value or the value evaluates to False (empty string), and no style.
Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.
If aggressive is True, empty cells with style are considered empty.
Return: bool
2613 def get_named_ranges( # type: ignore 2614 self, 2615 table_name: str | list[str] | None = None, 2616 ) -> list[NamedRange]: 2617 """Returns the list of available Name Ranges of the spreadsheet. If 2618 table_name is provided, limits the search to these tables. 2619 Beware : named ranges are stored at the body level, thus do not call 2620 this method on a cloned table. 2621 2622 Arguments: 2623 2624 table_names -- str or list of str, names of tables 2625 2626 Return : list of table_range 2627 """ 2628 body = self.document_body 2629 if not body: 2630 return [] 2631 all_named_ranges = body.get_named_ranges() 2632 if not table_name: 2633 return all_named_ranges # type:ignore 2634 filter_ = [] 2635 if isinstance(table_name, str): 2636 filter_.append(table_name) 2637 elif isiterable(table_name): 2638 filter_.extend(table_name) 2639 else: 2640 raise ValueError( 2641 f"table_name must be string or Iterable, not {type(table_name)}" 2642 ) 2643 return [ 2644 nr for nr in all_named_ranges if nr.table_name in filter_ # type:ignore 2645 ]
Returns the list of available Name Ranges of the spreadsheet. If table_name is provided, limits the search to these tables. Beware : named ranges are stored at the body level, thus do not call this method on a cloned table.
Arguments:
table_names -- str or list of str, names of tables
Return : list of table_range
2647 def get_named_range(self, name: str) -> NamedRange: 2648 """Returns the Name Ranges of the specified name. If 2649 table_name is provided, limits the search to these tables. 2650 Beware : named ranges are stored at the body level, thus do not call 2651 this method on a cloned table. 2652 2653 Arguments: 2654 2655 name -- str, name of the named range object 2656 2657 Return : NamedRange 2658 """ 2659 body = self.document_body 2660 if not body: 2661 raise ValueError("Table is not inside a document") 2662 return body.get_named_range(name) # type: ignore
Returns the Name Ranges of the specified name. If table_name is provided, limits the search to these tables. Beware : named ranges are stored at the body level, thus do not call this method on a cloned table.
Arguments:
name -- str, name of the named range object
Return : NamedRange
2664 def set_named_range( 2665 self, 2666 name: str, 2667 crange: str | tuple | list, 2668 table_name: str | None = None, 2669 usage: str | None = None, 2670 ) -> None: 2671 """Create a Named Range element and insert it in the document. 2672 Beware : named ranges are stored at the body level, thus do not call 2673 this method on a cloned table. 2674 2675 Arguments: 2676 2677 name -- str, name of the named range 2678 2679 crange -- str or tuple of int, cell or area coordinate 2680 2681 table_name -- str, name of the table 2682 2683 uage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row' 2684 """ 2685 body = self.document_body 2686 if not body: 2687 raise ValueError("Table is not inside a document") 2688 if not name: 2689 raise ValueError("Name required.") 2690 if table_name is None: 2691 table_name = self.name 2692 named_range = NamedRange(name, crange, table_name, usage) 2693 body.append_named_range(named_range)
Create a Named Range element and insert it in the document. Beware : named ranges are stored at the body level, thus do not call this method on a cloned table.
Arguments:
name -- str, name of the named range
crange -- str or tuple of int, cell or area coordinate
table_name -- str, name of the table
uage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row'
2695 def delete_named_range(self, name: str) -> None: 2696 """Delete the Named Range of specified name from the spreadsheet. 2697 Beware : named ranges are stored at the body level, thus do not call 2698 this method on a cloned table. 2699 2700 Arguments: 2701 2702 name -- str 2703 """ 2704 name = name.strip() 2705 if not name: 2706 raise ValueError("Name required.") 2707 body = self.document_body 2708 if not body: 2709 raise ValueError("Table is not inside a document.") 2710 body.delete_named_range(name)
Delete the Named Range of specified name from the spreadsheet. Beware : named ranges are stored at the body level, thus do not call this method on a cloned table.
Arguments:
name -- str
2716 def set_span( # noqa: C901 2717 self, 2718 area: str | tuple | list, 2719 merge: bool = False, 2720 ) -> bool: 2721 """Create a Cell Span : span the first cell of the area on several 2722 columns and/or rows. 2723 If merge is True, replace text of the cell by the concatenation of 2724 existing text in covered cells. 2725 Beware : if merge is True, old text is changed, if merge is False 2726 (the default), old text in coverd cells is still present but not 2727 displayed by most GUI. 2728 2729 If the area defines only one cell, the set span will do nothing. 2730 It is not allowed to apply set span to an area whose one cell already 2731 belongs to previous cell span. 2732 2733 Area can be either one cell (like 'A1') or an area ('A1:B2'). It can 2734 be provided as an alpha numeric value like "A1:B2' or a tuple like 2735 (0, 0, 1, 1) or (0, 0). 2736 2737 Arguments: 2738 2739 area -- str or tuple of int, cell or area coordinate 2740 2741 merge -- boolean 2742 """ 2743 # get area 2744 digits = convert_coordinates(area) 2745 if len(digits) == 4: 2746 x, y, z, t = digits 2747 else: 2748 x, y = digits 2749 z, t = digits 2750 start = x, y 2751 end = z, t 2752 if start == end: 2753 # one cell : do nothing 2754 return False 2755 if x is None: 2756 raise ValueError 2757 if y is None: 2758 raise ValueError 2759 if z is None: 2760 raise ValueError 2761 if t is None: 2762 raise ValueError 2763 # check for previous span 2764 good = True 2765 # Check boundaries and empty cells : need to crate non existent cells 2766 # so don't use get_cells directly, but get_cell 2767 cells = [] 2768 for yy in range(y, t + 1): 2769 row_cells = [] 2770 for xx in range(x, z + 1): 2771 row_cells.append( 2772 self.get_cell((xx, yy), clone=True, keep_repeated=False) 2773 ) 2774 cells.append(row_cells) 2775 for row in cells: 2776 for cell in row: 2777 if cell.is_spanned(): 2778 good = False 2779 break 2780 if not good: 2781 break 2782 if not good: 2783 return False 2784 # Check boundaries 2785 # if z >= self.width or t >= self.height: 2786 # self.set_cell(coord = end) 2787 # print area, z, t 2788 # cells = self.get_cells((x, y, z, t)) 2789 # print cells 2790 # do it: 2791 if merge: 2792 val_list = [] 2793 for row in cells: 2794 for cell in row: 2795 if cell.is_empty(aggressive=True): 2796 continue 2797 val = cell.get_value() 2798 if val is not None: 2799 if isinstance(val, str): 2800 val.strip() 2801 if val != "": 2802 val_list.append(val) 2803 cell.clear() 2804 if val_list: 2805 if len(val_list) == 1: 2806 cells[0][0].set_value(val_list[0]) 2807 else: 2808 value = " ".join([str(v) for v in val_list if v]) 2809 cells[0][0].set_value(value) 2810 cols = z - x + 1 2811 cells[0][0].set_attribute("table:number-columns-spanned", str(cols)) 2812 rows = t - y + 1 2813 cells[0][0].set_attribute("table:number-rows-spanned", str(rows)) 2814 for cell in cells[0][1:]: 2815 cell.tag = "table:covered-table-cell" 2816 for row in cells[1:]: 2817 for cell in row: 2818 cell.tag = "table:covered-table-cell" 2819 # replace cells in table 2820 self.set_cells(cells, coord=start, clone=False) 2821 return True
Create a Cell Span : span the first cell of the area on several columns and/or rows. If merge is True, replace text of the cell by the concatenation of existing text in covered cells. Beware : if merge is True, old text is changed, if merge is False (the default), old text in coverd cells is still present but not displayed by most GUI.
If the area defines only one cell, the set span will do nothing. It is not allowed to apply set span to an area whose one cell already belongs to previous cell span.
Area can be either one cell (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0).
Arguments:
area -- str or tuple of int, cell or area coordinate
merge -- boolean
2823 def del_span(self, area: str | tuple | list) -> bool: 2824 """Delete a Cell Span. 'area' is the cell coordiante of the upper left 2825 cell of the spanned area. 2826 2827 Area can be either one cell (like 'A1') or an area ('A1:B2'). It can 2828 be provided as an alpha numeric value like "A1:B2' or a tuple like 2829 (0, 0, 1, 1) or (0, 0). If an area is provided, the upper left cell 2830 is used. 2831 2832 Arguments: 2833 2834 area -- str or tuple of int, cell or area coordinate 2835 """ 2836 # get area 2837 digits = convert_coordinates(area) 2838 if len(digits) == 4: 2839 x, y, _z, _t = digits 2840 else: 2841 x, y = digits 2842 if x is None: 2843 raise ValueError 2844 if y is None: 2845 raise ValueError 2846 start = x, y 2847 # check for previous span 2848 cell0 = self.get_cell(start) 2849 nb_cols = cell0.get_attribute_integer("table:number-columns-spanned") 2850 if nb_cols is None: 2851 return False 2852 nb_rows = cell0.get_attribute_integer("table:number-rows-spanned") 2853 if nb_rows is None: 2854 return False 2855 z = x + nb_cols - 1 2856 t = y + nb_rows - 1 2857 cells = self.get_cells((x, y, z, t)) 2858 cells[0][0].del_attribute("table:number-columns-spanned") 2859 cells[0][0].del_attribute("table:number-rows-spanned") 2860 for cell in cells[0][1:]: 2861 cell.tag = "table:table-cell" 2862 for row in cells[1:]: 2863 for cell in row: 2864 cell.tag = "table:table-cell" 2865 # replace cells in table 2866 self.set_cells(cells, coord=start, clone=False) 2867 return True
Delete a Cell Span. 'area' is the cell coordiante of the upper left cell of the spanned area.
Area can be either one cell (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0). If an area is provided, the upper left cell is used.
Arguments:
area -- str or tuple of int, cell or area coordinate
2871 def to_csv( 2872 self, 2873 path_or_file: str | Path | None = None, 2874 dialect: str = "excel", 2875 ) -> Any: 2876 """Write the table as CSV in the file. 2877 2878 If the file is a string, it is opened as a local path. Else an 2879 opened file-like is expected. 2880 2881 Arguments: 2882 2883 path_or_file -- str or file-like 2884 2885 dialect -- str, python csv.dialect, can be 'excel', 'unix'... 2886 """ 2887 2888 def write_content(csv_writer: object) -> None: 2889 for values in self.iter_values(): 2890 line = [] 2891 for value in values: 2892 if value is None: 2893 value = "" 2894 if isinstance(value, str): 2895 value = value.strip() 2896 line.append(value) 2897 csv_writer.writerow(line) # type: ignore 2898 2899 out = StringIO(newline="") 2900 csv_writer = csv.writer(out, dialect=dialect) 2901 write_content(csv_writer) 2902 if path_or_file is None: 2903 return out.getvalue() 2904 path = Path(path_or_file) 2905 path.write_text(out.getvalue()) 2906 return None
Write the table as CSV in the file.
If the file is a string, it is opened as a local path. Else an opened file-like is expected.
Arguments:
path_or_file -- str or file-like
dialect -- str, python csv.dialect, can be 'excel', 'unix'...
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- get_between
- insert
- extend
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- append_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
275class Text(str): 276 """Representation of an XML text node. Created to hide the specifics of 277 lxml in searching text nodes using XPath. 278 279 Constructed like any str object but only accepts lxml text objects. 280 """ 281 282 # There's some black magic in inheriting from str 283 def __init__( 284 self, 285 text_result: _ElementUnicodeResult | _ElementStringResult, 286 ) -> None: 287 self.__parent = text_result.getparent() 288 self.__is_text = text_result.is_text 289 self.__is_tail = text_result.is_tail 290 291 @property 292 def parent(self) -> Element | None: 293 parent = self.__parent 294 # XXX happens just because of the unit test 295 if parent is None: 296 return None 297 return Element.from_tag(tag_or_elem=parent) 298 299 def is_text(self) -> bool: 300 return self.__is_text 301 302 def is_tail(self) -> bool: 303 return self.__is_tail
Representation of an XML text node. Created to hide the specifics of lxml in searching text nodes using XPath.
Constructed like any str object but only accepts lxml text objects.
Inherited Members
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
508class TextChange(Element): 509 """The TextChange "text:change" element marks a position in an empty 510 region where text has been deleted. 511 """ 512 513 _tag = "text:change" 514 515 def get_id(self) -> str | None: 516 return self.get_attribute_string("text:change-id") 517 518 def set_id(self, idx: str) -> None: 519 self.set_attribute("text:change-id", idx) 520 521 def _get_tracked_changes(self) -> Element | None: 522 body = self.document_body 523 if not body: 524 raise ValueError 525 return body.get_tracked_changes() 526 527 def get_changed_region( 528 self, 529 tracked_changes: Element | None = None, 530 ) -> Element | None: 531 if not tracked_changes: 532 tracked_changes = self._get_tracked_changes() 533 idx = self.get_id() 534 return tracked_changes.get_changed_region(text_id=idx) # type: ignore 535 536 def get_change_info( 537 self, 538 tracked_changes: Element | None = None, 539 ) -> Element | None: 540 changed_region = self.get_changed_region(tracked_changes=tracked_changes) 541 if not changed_region: 542 return None 543 return changed_region.get_change_info() # type: ignore 544 545 def get_change_element( 546 self, 547 tracked_changes: Element | None = None, 548 ) -> Element | None: 549 changed_region = self.get_changed_region(tracked_changes=tracked_changes) 550 if not changed_region: 551 return None 552 return changed_region.get_change_element() # type: ignore 553 554 def get_deleted( 555 self, 556 tracked_changes: Element | None = None, 557 as_text: bool = False, 558 no_header: bool = False, 559 clean: bool = True, 560 ) -> Element | None: 561 """Shortcut to get the deleted informations stored in the 562 TextDeletion stored in the tracked changes. 563 564 Return: Paragraph (or None)." 565 """ 566 changed = self.get_change_element(tracked_changes=tracked_changes) 567 if not changed: 568 return None 569 return changed.get_deleted( # type: ignore 570 as_text=as_text, 571 no_header=no_header, 572 clean=clean, 573 ) 574 575 def get_inserted( 576 self, 577 as_text: bool = False, 578 no_header: bool = False, 579 clean: bool = True, 580 ) -> str | Element | list[Element] | None: 581 """Return None.""" 582 return None 583 584 def get_start(self) -> TextChangeStart | None: 585 """Return None.""" 586 return None 587 588 def get_end(self) -> TextChangeEnd | None: 589 """Return None.""" 590 return None
The TextChange "text:change" element marks a position in an empty region where text has been deleted.
554 def get_deleted( 555 self, 556 tracked_changes: Element | None = None, 557 as_text: bool = False, 558 no_header: bool = False, 559 clean: bool = True, 560 ) -> Element | None: 561 """Shortcut to get the deleted informations stored in the 562 TextDeletion stored in the tracked changes. 563 564 Return: Paragraph (or None)." 565 """ 566 changed = self.get_change_element(tracked_changes=tracked_changes) 567 if not changed: 568 return None 569 return changed.get_deleted( # type: ignore 570 as_text=as_text, 571 no_header=no_header, 572 clean=clean, 573 )
Shortcut to get the deleted informations stored in the TextDeletion stored in the tracked changes.
Return: Paragraph (or None)."
575 def get_inserted( 576 self, 577 as_text: bool = False, 578 no_header: bool = False, 579 clean: bool = True, 580 ) -> str | Element | list[Element] | None: 581 """Return None.""" 582 return None
Return None.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
593class TextChangeEnd(TextChange): 594 """The TextChangeEnd "text:change-end" element marks the end of a region 595 with content where text has been inserted or the format has been 596 changed. 597 """ 598 599 _tag = "text:change-end" 600 601 def get_start(self) -> TextChangeStart | None: 602 """Return the corresponding annotation starting tag or None.""" 603 idx = self.get_id() 604 parent = self.parent 605 if parent is None: 606 raise ValueError("Can not find end tag: no parent available.") 607 body = self.document_body 608 if not body: 609 body = self.root 610 return body.get_text_change_start(idx=idx) # type: ignore 611 612 def get_end(self) -> TextChangeEnd | None: 613 """Return self.""" 614 return self 615 616 def get_deleted(self, *args: Any, **kwargs: Any) -> Element | None: 617 """Return None.""" 618 return None 619 620 def get_inserted( 621 self, 622 as_text: bool = False, 623 no_header: bool = False, 624 clean: bool = True, 625 ) -> str | Element | list[Element] | None: 626 """Return the content between text:change-start and text:change-end. 627 628 If no content exists (deletion tag), returns None (or '' if text flag 629 is True). 630 If as_text is True: returns the text content. 631 If clean is True: suppress unwanted tags (deletions marks, ...) 632 If no_header is True: existing text:h are changed in text:p 633 By default: returns a list of Element, cleaned and with headers 634 635 Arguments: 636 637 as_text -- boolean 638 639 clean -- boolean 640 641 no_header -- boolean 642 643 Return: list or Element or text 644 """ 645 646 # idx = self.get_id() 647 start = self.get_start() 648 end = self.get_end() 649 if end is None or start is None: 650 if as_text: 651 return "" 652 return None 653 body = self.document_body 654 if not body: 655 body = self.root 656 return body.get_between( 657 start, end, as_text=as_text, no_header=no_header, clean=clean 658 )
The TextChangeEnd "text:change-end" element marks the end of a region with content where text has been inserted or the format has been changed.
601 def get_start(self) -> TextChangeStart | None: 602 """Return the corresponding annotation starting tag or None.""" 603 idx = self.get_id() 604 parent = self.parent 605 if parent is None: 606 raise ValueError("Can not find end tag: no parent available.") 607 body = self.document_body 608 if not body: 609 body = self.root 610 return body.get_text_change_start(idx=idx) # type: ignore
Return the corresponding annotation starting tag or None.
616 def get_deleted(self, *args: Any, **kwargs: Any) -> Element | None: 617 """Return None.""" 618 return None
Return None.
620 def get_inserted( 621 self, 622 as_text: bool = False, 623 no_header: bool = False, 624 clean: bool = True, 625 ) -> str | Element | list[Element] | None: 626 """Return the content between text:change-start and text:change-end. 627 628 If no content exists (deletion tag), returns None (or '' if text flag 629 is True). 630 If as_text is True: returns the text content. 631 If clean is True: suppress unwanted tags (deletions marks, ...) 632 If no_header is True: existing text:h are changed in text:p 633 By default: returns a list of Element, cleaned and with headers 634 635 Arguments: 636 637 as_text -- boolean 638 639 clean -- boolean 640 641 no_header -- boolean 642 643 Return: list or Element or text 644 """ 645 646 # idx = self.get_id() 647 start = self.get_start() 648 end = self.get_end() 649 if end is None or start is None: 650 if as_text: 651 return "" 652 return None 653 body = self.document_body 654 if not body: 655 body = self.root 656 return body.get_between( 657 start, end, as_text=as_text, no_header=no_header, clean=clean 658 )
Return the content between text:change-start and text:change-end.
If no content exists (deletion tag), returns None (or '' if text flag is True). If as_text is True: returns the text content. If clean is True: suppress unwanted tags (deletions marks, ...) If no_header is True: existing text:h are changed in text:p By default: returns a list of Element, cleaned and with headers
Arguments:
as_text -- boolean
clean -- boolean
no_header -- boolean
Return: list or Element or text
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
661class TextChangeStart(TextChangeEnd): 662 """The TextChangeStart "text:change-start" element marks the start of a 663 region with content where text has been inserted or the format has 664 been changed. 665 """ 666 667 _tag = "text:change-start" 668 669 def get_start(self) -> TextChangeStart: 670 """Return self.""" 671 return self 672 673 def get_end(self) -> TextChangeEnd: 674 """Return the corresponding change-end tag or None.""" 675 idx = self.get_id() 676 parent = self.parent 677 if parent is None: 678 raise ValueError("Can not find end tag: no parent available.") 679 body = self.document_body 680 if not body: 681 body = self.root 682 return body.get_text_change_end(idx=idx) # type: ignore 683 684 def delete( 685 self, 686 child: Element | None = None, 687 keep_tail: bool = True, 688 ) -> None: 689 """Delete the given element from the XML tree. If no element is given, 690 "self" is deleted. The XML library may allow to continue to use an 691 element now "orphan" as long as you have a reference to it. 692 693 For TextChangeStart : delete also the end tag if exists. 694 695 Arguments: 696 697 child -- Element 698 699 keep_tail -- boolean (default to True), True for most usages. 700 """ 701 if child is not None: # act like normal delete 702 return super().delete(child, keep_tail) 703 idx = self.get_id() 704 parent = self.parent 705 if parent is None: 706 raise ValueError("cannot delete the root element") 707 body = self.document_body 708 if not body: 709 body = parent 710 end = body.get_text_change_end(idx=idx) 711 if end: 712 end.delete() 713 # act like normal delete 714 super().delete()
The TextChangeStart "text:change-start" element marks the start of a region with content where text has been inserted or the format has been changed.
673 def get_end(self) -> TextChangeEnd: 674 """Return the corresponding change-end tag or None.""" 675 idx = self.get_id() 676 parent = self.parent 677 if parent is None: 678 raise ValueError("Can not find end tag: no parent available.") 679 body = self.document_body 680 if not body: 681 body = self.root 682 return body.get_text_change_end(idx=idx) # type: ignore
Return the corresponding change-end tag or None.
684 def delete( 685 self, 686 child: Element | None = None, 687 keep_tail: bool = True, 688 ) -> None: 689 """Delete the given element from the XML tree. If no element is given, 690 "self" is deleted. The XML library may allow to continue to use an 691 element now "orphan" as long as you have a reference to it. 692 693 For TextChangeStart : delete also the end tag if exists. 694 695 Arguments: 696 697 child -- Element 698 699 keep_tail -- boolean (default to True), True for most usages. 700 """ 701 if child is not None: # act like normal delete 702 return super().delete(child, keep_tail) 703 idx = self.get_id() 704 parent = self.parent 705 if parent is None: 706 raise ValueError("cannot delete the root element") 707 body = self.document_body 708 if not body: 709 body = parent 710 end = body.get_text_change_end(idx=idx) 711 if end: 712 end.delete() 713 # act like normal delete 714 super().delete()
Delete the given element from the XML tree. If no element is given, "self" is deleted. The XML library may allow to continue to use an element now "orphan" as long as you have a reference to it.
For TextChangeStart : delete also the end tag if exists.
Arguments:
child -- Element
keep_tail -- boolean (default to True), True for most usages.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
358class TextChangedRegion(Element): 359 """Each TextChangedRegion "text:changed-region" element contains a single 360 element, one of TextInsertion, TextDeletion or TextFormatChange that 361 corresponds to a change being tracked within the scope of the 362 "text:tracked-changes" element that contains the "text:changed-region" 363 instance. 364 The xml:id attribute of the TextChangedRegion is referenced 365 from the "text:change", "text:change-start" and "text:change-end" 366 elements that identify where the change applies to markup in the scope of 367 the "text:tracked-changes" element. 368 369 Warning : for this implementation, text:change should be referenced only 370 once in the scope, which is different from ODF 1.2 requirement: 371 " A "text:changed-region" can be referenced by more than one 372 change, but the corresponding referencing change mark elements 373 shall be of the same change type - insertion, format change or 374 deletion. " 375 """ 376 377 _tag = "text:changed-region" 378 379 def get_change_info(self) -> Element | None: 380 """Shortcut to get the ChangeInfo element of the change 381 element child. 382 383 Return: ChangeInfo element. 384 """ 385 return self.get_element("descendant::office:change-info") 386 387 def set_change_info( 388 self, 389 change_info: Element | None = None, 390 creator: str | None = None, 391 date: datetime | None = None, 392 comments: Element | list[Element] | None = None, 393 ) -> None: 394 """Shortcut to set the ChangeInfo element of the sub change element. 395 See TextInsertion.set_change_info() for details. 396 397 Arguments: 398 399 change_info -- ChangeInfo element (or None) 400 401 cretor -- str (or None) 402 403 date -- datetime (or None) 404 405 comments -- Paragraph or list of Paragraph elements (or None) 406 """ 407 child = self.get_change_element() 408 if not child: 409 raise ValueError 410 child.set_change_info( # type: ignore 411 change_info=change_info, creator=creator, date=date, comments=comments 412 ) 413 414 def get_change_element(self) -> Element | None: 415 """Get the change element child. It can be either: TextInsertion, 416 TextDeletion, or TextFormatChange as an Element object. 417 418 Return: Element. 419 """ 420 request = ( 421 "descendant::text:insertion " 422 "| descendant::text:deletion" 423 "| descendant::text:format-change" 424 ) 425 return self._filtered_element(request, 0) 426 427 def _get_text_id(self) -> str | None: 428 return self.get_attribute_string("text:id") 429 430 def _set_text_id(self, text_id: str) -> None: 431 self.set_attribute("text:id", text_id) 432 433 def _get_xml_id(self) -> str | None: 434 return self.get_attribute_string("xml:id") 435 436 def _set_xml_id(self, xml_id: str) -> None: 437 self.set_attribute("xml:id", xml_id) 438 439 def get_id(self) -> str | None: 440 """Get the "text:id" attribute. 441 442 Return: str 443 """ 444 return self._get_text_id() 445 446 def set_id(self, idx: str) -> None: 447 """Set both the "text:id" and "xml:id" attributes with same value.""" 448 self._set_text_id(idx) 449 self._set_xml_id(idx)
Each TextChangedRegion "text:changed-region" element contains a single element, one of TextInsertion, TextDeletion or TextFormatChange that corresponds to a change being tracked within the scope of the "text:tracked-changes" element that contains the "text:changed-region" instance. The xml:id attribute of the TextChangedRegion is referenced from the "text:change", "text:change-start" and "text:change-end" elements that identify where the change applies to markup in the scope of the "text:tracked-changes" element.
Warning : for this implementation, text:change should be referenced only once in the scope, which is different from ODF 1.2 requirement: " A "text:changed-region" can be referenced by more than one change, but the corresponding referencing change mark elements shall be of the same change type - insertion, format change or deletion. "
379 def get_change_info(self) -> Element | None: 380 """Shortcut to get the ChangeInfo element of the change 381 element child. 382 383 Return: ChangeInfo element. 384 """ 385 return self.get_element("descendant::office:change-info")
Shortcut to get the ChangeInfo element of the change element child.
Return: ChangeInfo element.
387 def set_change_info( 388 self, 389 change_info: Element | None = None, 390 creator: str | None = None, 391 date: datetime | None = None, 392 comments: Element | list[Element] | None = None, 393 ) -> None: 394 """Shortcut to set the ChangeInfo element of the sub change element. 395 See TextInsertion.set_change_info() for details. 396 397 Arguments: 398 399 change_info -- ChangeInfo element (or None) 400 401 cretor -- str (or None) 402 403 date -- datetime (or None) 404 405 comments -- Paragraph or list of Paragraph elements (or None) 406 """ 407 child = self.get_change_element() 408 if not child: 409 raise ValueError 410 child.set_change_info( # type: ignore 411 change_info=change_info, creator=creator, date=date, comments=comments 412 )
Shortcut to set the ChangeInfo element of the sub change element. See TextInsertion.set_change_info() for details.
Arguments:
change_info -- ChangeInfo element (or None)
cretor -- str (or None)
date -- datetime (or None)
comments -- Paragraph or list of Paragraph elements (or None)
414 def get_change_element(self) -> Element | None: 415 """Get the change element child. It can be either: TextInsertion, 416 TextDeletion, or TextFormatChange as an Element object. 417 418 Return: Element. 419 """ 420 request = ( 421 "descendant::text:insertion " 422 "| descendant::text:deletion" 423 "| descendant::text:format-change" 424 ) 425 return self._filtered_element(request, 0)
Get the change element child. It can be either: TextInsertion, TextDeletion, or TextFormatChange as an Element object.
Return: Element.
439 def get_id(self) -> str | None: 440 """Get the "text:id" attribute. 441 442 Return: str 443 """ 444 return self._get_text_id()
Get the "text:id" attribute.
Return: str
446 def set_id(self, idx: str) -> None: 447 """Set both the "text:id" and "xml:id" attributes with same value.""" 448 self._set_text_id(idx) 449 self._set_xml_id(idx)
Set both the "text:id" and "xml:id" attributes with same value.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
251class TextDeletion(TextInsertion): 252 """The TextDeletion "text:deletion" contains information that identifies 253 the person responsible for a deletion and the date of that deletion. 254 This information may also contain one or more Paragraph which contains 255 a comment on the deletion. The TextDeletion element may also contain 256 content that was deleted while change tracking was enabled. The position 257 where the text was deleted is marked by a "text:change" element. Deleted 258 text is contained in a paragraph element. To reconstruct the original 259 text, the paragraph containing the deleted text is merged with its 260 surrounding paragraph or heading element. To reconstruct the text before 261 a deletion took place: 262 - If the change mark is inside a paragraph, insert the content that was 263 deleted, but remove all leading start tags up to and including the 264 first "text:p" element and all trailing end tags up to and including 265 the last "/text:p" or "/text:h" element. If the last trailing element 266 is a "/text:h", change the end tag "/text:p" following this insertion 267 to a "/text:h" element. 268 - If the change mark is inside a heading, insert the content that was 269 deleted, but remove all leading start tags up to and including the 270 first "text:h" element and all trailing end tags up to and including 271 the last "/text:h" or "/text:p" element. If the last trailing element 272 is a "/text:p", change the end tag "/text:h" following this insertion 273 to a "/text:p" element. 274 - Otherwise, copy the text content of the "text:deletion" element in 275 place of the change mark. 276 """ 277 278 _tag = "text:deletion" 279 280 def get_deleted( 281 self, 282 as_text: bool = False, 283 no_header: bool = False, 284 ) -> str | list[Element] | None: 285 """Get the deleted informations stored in the TextDeletion. 286 If as_text is True: returns the text content. 287 If no_header is True: existing Heading are changed in Paragraph 288 289 Arguments: 290 291 as_text -- boolean 292 293 no_header -- boolean 294 295 Return: Paragraph and Header list 296 """ 297 children = self.children 298 inner = [elem for elem in children if elem.tag != "office:change-info"] 299 if no_header: # crude replace t:h by t:p 300 new_inner = [] 301 for element in inner: 302 if element.tag == "text:h": 303 children = element.children 304 text = element.text 305 para = Element.from_tag("text:p") 306 para.text = text 307 for child in children: 308 para.append(child) 309 new_inner.append(para) 310 else: 311 new_inner.append(element) 312 inner = new_inner 313 if as_text: 314 return "\n".join([elem.get_formatted_text(context=None) for elem in inner]) 315 return inner 316 317 def set_deleted(self, paragraph_or_list: Element | list[Element]) -> None: 318 """Set the deleted informations stored in the TextDeletion. An 319 actual content that was deleted is expected, embeded in a Paragraph 320 element or Header. 321 322 Arguments: 323 324 paragraph_or_list -- Paragraph or Header element (or list) 325 """ 326 for element in self.get_deleted(): # type: ignore 327 self.delete(element) # type: ignore 328 if isinstance(paragraph_or_list, Element): 329 paragraph_or_list = [paragraph_or_list] 330 for element in paragraph_or_list: 331 self.append(element) 332 333 def get_inserted( 334 self, 335 as_text: bool = False, 336 no_header: bool = False, 337 clean: bool = True, 338 ) -> str | Element | list[Element] | None: 339 """Return None.""" 340 if as_text: 341 return "" 342 return None
The TextDeletion "text:deletion" contains information that identifies the person responsible for a deletion and the date of that deletion. This information may also contain one or more Paragraph which contains a comment on the deletion. The TextDeletion element may also contain content that was deleted while change tracking was enabled. The position where the text was deleted is marked by a "text:change" element. Deleted text is contained in a paragraph element. To reconstruct the original text, the paragraph containing the deleted text is merged with its surrounding paragraph or heading element. To reconstruct the text before a deletion took place:
- If the change mark is inside a paragraph, insert the content that was deleted, but remove all leading start tags up to and including the first "text:p" element and all trailing end tags up to and including the last "/text:p" or "/text:h" element. If the last trailing element is a "/text:h", change the end tag "/text:p" following this insertion to a "/text:h" element.
- If the change mark is inside a heading, insert the content that was deleted, but remove all leading start tags up to and including the first "text:h" element and all trailing end tags up to and including the last "/text:h" or "/text:p" element. If the last trailing element is a "/text:p", change the end tag "/text:h" following this insertion to a "/text:p" element.
- Otherwise, copy the text content of the "text:deletion" element in place of the change mark.
280 def get_deleted( 281 self, 282 as_text: bool = False, 283 no_header: bool = False, 284 ) -> str | list[Element] | None: 285 """Get the deleted informations stored in the TextDeletion. 286 If as_text is True: returns the text content. 287 If no_header is True: existing Heading are changed in Paragraph 288 289 Arguments: 290 291 as_text -- boolean 292 293 no_header -- boolean 294 295 Return: Paragraph and Header list 296 """ 297 children = self.children 298 inner = [elem for elem in children if elem.tag != "office:change-info"] 299 if no_header: # crude replace t:h by t:p 300 new_inner = [] 301 for element in inner: 302 if element.tag == "text:h": 303 children = element.children 304 text = element.text 305 para = Element.from_tag("text:p") 306 para.text = text 307 for child in children: 308 para.append(child) 309 new_inner.append(para) 310 else: 311 new_inner.append(element) 312 inner = new_inner 313 if as_text: 314 return "\n".join([elem.get_formatted_text(context=None) for elem in inner]) 315 return inner
Get the deleted informations stored in the TextDeletion. If as_text is True: returns the text content. If no_header is True: existing Heading are changed in Paragraph
Arguments:
as_text -- boolean
no_header -- boolean
Return: Paragraph and Header list
317 def set_deleted(self, paragraph_or_list: Element | list[Element]) -> None: 318 """Set the deleted informations stored in the TextDeletion. An 319 actual content that was deleted is expected, embeded in a Paragraph 320 element or Header. 321 322 Arguments: 323 324 paragraph_or_list -- Paragraph or Header element (or list) 325 """ 326 for element in self.get_deleted(): # type: ignore 327 self.delete(element) # type: ignore 328 if isinstance(paragraph_or_list, Element): 329 paragraph_or_list = [paragraph_or_list] 330 for element in paragraph_or_list: 331 self.append(element)
Set the deleted informations stored in the TextDeletion. An actual content that was deleted is expected, embeded in a Paragraph element or Header.
Arguments:
paragraph_or_list -- Paragraph or Header element (or list)
333 def get_inserted( 334 self, 335 as_text: bool = False, 336 no_header: bool = False, 337 clean: bool = True, 338 ) -> str | Element | list[Element] | None: 339 """Return None.""" 340 if as_text: 341 return "" 342 return None
Return None.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
345class TextFormatChange(TextInsertion): 346 """The TextFormatChange "text:format-change" element represents any change 347 in formatting attributes. The region where the change took place is 348 marked by "text:change-start", "text:change-end" or "text:change" 349 elements. 350 351 Note: This element does not contain formatting changes that have taken 352 place. 353 """ 354 355 _tag = "text:format-change"
The TextFormatChange "text:format-change" element represents any change in formatting attributes. The region where the change took place is marked by "text:change-start", "text:change-end" or "text:change" elements.
Note: This element does not contain formatting changes that have taken place.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
134class TextInsertion(Element): 135 """The TextInsertion "text:insertion" element contains the information 136 that identifies the person responsible for a change and the date of 137 that change. This information may also contain one or more "text:p" 138 Paragraph which contain a comment on the insertion. The 139 TextInsertion element's parent "text:changed-region" element has an 140 xml:id or text:id attribute, the value of which binds that parent 141 element to the text:change-id attribute on the "text:change-start" 142 and "text:change-end" elements. 143 """ 144 145 _tag = "text:insertion" 146 147 def get_deleted( 148 self, 149 as_text: bool = False, 150 no_header: bool = False, 151 ) -> str | list[Element] | None: 152 """Return: None.""" 153 if as_text: 154 return "" 155 return None 156 157 def get_inserted( 158 self, 159 as_text: bool = False, 160 no_header: bool = False, 161 clean: bool = True, 162 ) -> str | Element | list[Element] | None: 163 """Shortcut to text:change-start.get_inserted(). Return the content 164 between text:change-start and text:change-end. 165 166 If as_text is True: returns the text content. 167 If no_header is True: existing Heading are changed in Paragraph 168 If no_header is True: existing text:h are changed in text:p 169 By default: returns a list of Element, cleaned and with headers 170 171 Arguments: 172 173 as_text -- boolean 174 175 clean -- boolean 176 177 no_header -- boolean 178 179 Return: list or Element or text 180 """ 181 current = self.parent # text:changed-region 182 if not current: 183 raise ValueError 184 idx = current.get_id() # type: ignore 185 body = self.document_body 186 if not body: 187 body = self.root 188 text_change = body.get_text_change_start(idx=idx) 189 if not text_change: 190 raise ValueError 191 return text_change.get_inserted( # type: ignore 192 as_text=as_text, no_header=no_header, clean=clean 193 ) 194 195 def get_change_info(self) -> Element | None: 196 """Get the ChangeInfo child of the element. 197 198 Return: ChangeInfo element. 199 """ 200 return self.get_element("descendant::office:change-info") 201 202 def set_change_info( 203 self, 204 change_info: Element | None = None, 205 creator: str | None = None, 206 date: datetime | None = None, 207 comments: Element | list[Element] | None = None, 208 ) -> None: 209 """Set the ChangeInfo element for the change element. If change_info 210 is not provided, creator, date and comments will be used to build a 211 suitable change info element. Default for creator is 'Unknown', 212 default for date is current time and default for comments is no 213 comment at all. 214 The new change info element will replace any existant ChangeInfo. 215 216 Arguments: 217 218 change_info -- ChangeInfo element (or None) 219 220 cretor -- str (or None) 221 222 date -- datetime (or None) 223 224 comments -- Paragraph or list of Paragraph elements (or None) 225 """ 226 if change_info is None: 227 new_change_info = ChangeInfo(creator, date) 228 if comments is not None: 229 if isinstance(comments, Element): 230 # single pararagraph comment 231 comments_list = [comments] 232 else: 233 comments_list = comments 234 # assume iterable of Paragraph 235 for paragraph in comments_list: 236 if not isinstance(paragraph, Paragraph): 237 raise TypeError(f"Not a Paragraph: '{paragraph!r}'") 238 new_change_info.insert(paragraph, xmlposition=LAST_CHILD) 239 else: 240 if not isinstance(change_info, ChangeInfo): 241 raise TypeError(f"Not a ChangeInfo: '{change_info!r}'") 242 new_change_info = change_info 243 244 old = self.get_change_info() 245 if old is not None: 246 self.replace_element(old, new_change_info) 247 else: 248 self.insert(new_change_info, xmlposition=FIRST_CHILD)
The TextInsertion "text:insertion" element contains the information that identifies the person responsible for a change and the date of that change. This information may also contain one or more "text:p" Paragraph which contain a comment on the insertion. The TextInsertion element's parent "text:changed-region" element has an xml:id or text:id attribute, the value of which binds that parent element to the text:change-id attribute on the "text:change-start" and "text:change-end" elements.
147 def get_deleted( 148 self, 149 as_text: bool = False, 150 no_header: bool = False, 151 ) -> str | list[Element] | None: 152 """Return: None.""" 153 if as_text: 154 return "" 155 return None
Return: None.
157 def get_inserted( 158 self, 159 as_text: bool = False, 160 no_header: bool = False, 161 clean: bool = True, 162 ) -> str | Element | list[Element] | None: 163 """Shortcut to text:change-start.get_inserted(). Return the content 164 between text:change-start and text:change-end. 165 166 If as_text is True: returns the text content. 167 If no_header is True: existing Heading are changed in Paragraph 168 If no_header is True: existing text:h are changed in text:p 169 By default: returns a list of Element, cleaned and with headers 170 171 Arguments: 172 173 as_text -- boolean 174 175 clean -- boolean 176 177 no_header -- boolean 178 179 Return: list or Element or text 180 """ 181 current = self.parent # text:changed-region 182 if not current: 183 raise ValueError 184 idx = current.get_id() # type: ignore 185 body = self.document_body 186 if not body: 187 body = self.root 188 text_change = body.get_text_change_start(idx=idx) 189 if not text_change: 190 raise ValueError 191 return text_change.get_inserted( # type: ignore 192 as_text=as_text, no_header=no_header, clean=clean 193 )
Shortcut to text:change-start.get_inserted(). Return the content between text:change-start and text:change-end.
If as_text is True: returns the text content. If no_header is True: existing Heading are changed in Paragraph If no_header is True: existing text:h are changed in text:p By default: returns a list of Element, cleaned and with headers
Arguments:
as_text -- boolean
clean -- boolean
no_header -- boolean
Return: list or Element or text
195 def get_change_info(self) -> Element | None: 196 """Get the ChangeInfo child of the element. 197 198 Return: ChangeInfo element. 199 """ 200 return self.get_element("descendant::office:change-info")
Get the ChangeInfo child of the element.
Return: ChangeInfo element.
202 def set_change_info( 203 self, 204 change_info: Element | None = None, 205 creator: str | None = None, 206 date: datetime | None = None, 207 comments: Element | list[Element] | None = None, 208 ) -> None: 209 """Set the ChangeInfo element for the change element. If change_info 210 is not provided, creator, date and comments will be used to build a 211 suitable change info element. Default for creator is 'Unknown', 212 default for date is current time and default for comments is no 213 comment at all. 214 The new change info element will replace any existant ChangeInfo. 215 216 Arguments: 217 218 change_info -- ChangeInfo element (or None) 219 220 cretor -- str (or None) 221 222 date -- datetime (or None) 223 224 comments -- Paragraph or list of Paragraph elements (or None) 225 """ 226 if change_info is None: 227 new_change_info = ChangeInfo(creator, date) 228 if comments is not None: 229 if isinstance(comments, Element): 230 # single pararagraph comment 231 comments_list = [comments] 232 else: 233 comments_list = comments 234 # assume iterable of Paragraph 235 for paragraph in comments_list: 236 if not isinstance(paragraph, Paragraph): 237 raise TypeError(f"Not a Paragraph: '{paragraph!r}'") 238 new_change_info.insert(paragraph, xmlposition=LAST_CHILD) 239 else: 240 if not isinstance(change_info, ChangeInfo): 241 raise TypeError(f"Not a ChangeInfo: '{change_info!r}'") 242 new_change_info = change_info 243 244 old = self.get_change_info() 245 if old is not None: 246 self.replace_element(old, new_change_info) 247 else: 248 self.insert(new_change_info, xmlposition=FIRST_CHILD)
Set the ChangeInfo element for the change element. If change_info is not provided, creator, date and comments will be used to build a suitable change info element. Default for creator is 'Unknown', default for date is current time and default for comments is no comment at all. The new change info element will replace any existant ChangeInfo.
Arguments:
change_info -- ChangeInfo element (or None)
cretor -- str (or None)
date -- datetime (or None)
comments -- Paragraph or list of Paragraph elements (or None)
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
467class TocEntryTemplate(Element): 468 """ODF "text:table-of-content-entry-template" 469 470 Arguments: 471 472 style -- str 473 """ 474 475 _tag = "text:table-of-content-entry-template" 476 _properties = (PropDef("style", "text:style-name"),) 477 478 def __init__( 479 self, 480 style: str | None = None, 481 outline_level: int | None = None, 482 **kwargs: Any, 483 ) -> None: 484 super().__init__(**kwargs) 485 if self._do_init: 486 if style: 487 self.style = style 488 if outline_level: 489 self.outline_level = outline_level 490 491 @property 492 def outline_level(self) -> int | None: 493 return self.get_attribute_integer("text:outline-level") 494 495 @outline_level.setter 496 def outline_level(self, level: int) -> None: 497 self.set_attribute("text:outline-level", str(level)) 498 499 def complete_defaults(self) -> None: 500 self.append(Element.from_tag("text:index-entry-chapter")) 501 self.append(Element.from_tag("text:index-entry-text")) 502 self.append(Element.from_tag("text:index-entry-text")) 503 ts = Element.from_tag("text:index-entry-text") 504 ts.set_style_attribute("style:type", "right") 505 ts.set_style_attribute("style:leader-char", ".") 506 self.append(ts) 507 self.append(Element.from_tag("text:index-entry-page-number"))
ODF "text:table-of-content-entry-template"
Arguments:
style -- str
499 def complete_defaults(self) -> None: 500 self.append(Element.from_tag("text:index-entry-chapter")) 501 self.append(Element.from_tag("text:index-entry-text")) 502 self.append(Element.from_tag("text:index-entry-text")) 503 ts = Element.from_tag("text:index-entry-text") 504 ts.set_style_attribute("style:type", "right") 505 ts.set_style_attribute("style:leader-char", ".") 506 self.append(ts) 507 self.append(Element.from_tag("text:index-entry-page-number"))
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
452class TrackedChanges(Element): 453 """The TrackedChanges "text:tracked-changes" element acts as a container 454 for TextChangedRegion elements that represent changes in a certain 455 scope of an OpenDocument document. This scope is the element in which 456 the TrackedChanges element occurs. Changes in this scope shall be 457 tracked by TextChangedRegion elements contained in the 458 TrackedChanges element in this scope. If a TrackedChanges 459 element is absent, there are no tracked changes in the corresponding 460 scope. In this case, all change mark elements in this scope shall be 461 ignored. 462 """ 463 464 _tag = "text:tracked-changes" 465 466 def get_changed_regions( 467 self, 468 creator: str | None = None, 469 date: datetime | None = None, 470 content: str | None = None, 471 role: str | None = None, 472 ) -> list[Element]: 473 changed_regions = self._filtered_elements( 474 "text:changed-region", 475 dc_creator=creator, 476 dc_date=date, 477 content=content, 478 ) 479 if role is None: 480 return changed_regions 481 result: list[Element] = [] 482 for regien in changed_regions: 483 changed = regien.get_change_element() # type: ignore 484 if not changed: 485 continue 486 if changed.tag.endswith(role): 487 result.append(regien) 488 return result 489 490 def get_changed_region( 491 self, 492 position: int = 0, 493 text_id: str | None = None, 494 creator: str | None = None, 495 date: datetime | None = None, 496 content: str | None = None, 497 ) -> Element | None: 498 return self._filtered_element( 499 "text:changed-region", 500 position, 501 text_id=text_id, 502 dc_creator=creator, 503 dc_date=date, 504 content=content, 505 )
The TrackedChanges "text:tracked-changes" element acts as a container for TextChangedRegion elements that represent changes in a certain scope of an OpenDocument document. This scope is the element in which the TrackedChanges element occurs. Changes in this scope shall be tracked by TextChangedRegion elements contained in the TrackedChanges element in this scope. If a TrackedChanges element is absent, there are no tracked changes in the corresponding scope. In this case, all change mark elements in this scope shall be ignored.
466 def get_changed_regions( 467 self, 468 creator: str | None = None, 469 date: datetime | None = None, 470 content: str | None = None, 471 role: str | None = None, 472 ) -> list[Element]: 473 changed_regions = self._filtered_elements( 474 "text:changed-region", 475 dc_creator=creator, 476 dc_date=date, 477 content=content, 478 ) 479 if role is None: 480 return changed_regions 481 result: list[Element] = [] 482 for regien in changed_regions: 483 changed = regien.get_change_element() # type: ignore 484 if not changed: 485 continue 486 if changed.tag.endswith(role): 487 result.append(regien) 488 return result
490 def get_changed_region( 491 self, 492 position: int = 0, 493 text_id: str | None = None, 494 creator: str | None = None, 495 date: datetime | None = None, 496 content: str | None = None, 497 ) -> Element | None: 498 return self._filtered_element( 499 "text:changed-region", 500 position, 501 text_id=text_id, 502 dc_creator=creator, 503 dc_date=date, 504 content=content, 505 )
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
212class UserDefined(ElementTyped): 213 """Return a user defined field "text:user-defined". If the current 214 document is provided, try to extract the content of the meta user defined 215 field of same name. 216 217 Arguments: 218 219 name -- str, name of the user defined field 220 221 value -- python typed value, value of the field 222 223 value_type -- str, office:value-type known type 224 225 text -- str 226 227 style -- str 228 229 from_document -- ODF document 230 """ 231 232 _tag = "text:user-defined" 233 _properties = ( 234 PropDef("name", "text:name"), 235 PropDef("style", "style:data-style-name"), 236 ) 237 238 def __init__( 239 self, 240 name: str = "", 241 value: Any = None, 242 value_type: str | None = None, 243 text: str | None = None, 244 style: str | None = None, 245 from_document: Document | None = None, 246 **kwargs: Any, 247 ) -> None: 248 super().__init__(**kwargs) 249 if self._do_init: 250 if name: 251 self.name = name 252 if style: 253 self.style = style 254 if from_document is not None: 255 meta_infos = from_document.meta 256 content = meta_infos.get_user_defined_metadata_of_name(name) 257 if content is not None: 258 value = content.get("value", None) 259 value_type = content.get("value_type", None) 260 text = content.get("text", None) 261 text = self.set_value_and_type( 262 value=value, value_type=value_type, text=text 263 ) 264 self.text = text # type: ignore
Return a user defined field "text:user-defined". If the current document is provided, try to extract the content of the meta user defined field of same name.
Arguments:
name -- str, name of the user defined field
value -- python typed value, value of the field
value_type -- str, office:value-type known type
text -- str
style -- str
from_document -- ODF document
238 def __init__( 239 self, 240 name: str = "", 241 value: Any = None, 242 value_type: str | None = None, 243 text: str | None = None, 244 style: str | None = None, 245 from_document: Document | None = None, 246 **kwargs: Any, 247 ) -> None: 248 super().__init__(**kwargs) 249 if self._do_init: 250 if name: 251 self.name = name 252 if style: 253 self.style = style 254 if from_document is not None: 255 meta_infos = from_document.meta 256 content = meta_infos.get_user_defined_metadata_of_name(name) 257 if content is not None: 258 value = content.get("value", None) 259 value_type = content.get("value_type", None) 260 text = content.get("text", None) 261 text = self.set_value_and_type( 262 value=value, value_type=value_type, text=text 263 ) 264 self.text = text # type: ignore
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
146class UserFieldDecl(ElementTyped): 147 _tag = "text:user-field-decl" 148 _properties = (PropDef("name", "text:name"),) 149 150 def __init__( 151 self, 152 name: str | None = None, 153 value: Any = None, 154 value_type: str | None = None, 155 **kwargs: Any, 156 ) -> None: 157 super().__init__(**kwargs) 158 if self._do_init: 159 if name: 160 self.name = name 161 self.set_value_and_type(value=value, value_type=value_type) 162 163 def set_value(self, value: Any) -> None: 164 name = self.get_attribute("text:name") 165 self.clear() 166 self.set_value_and_type(value=value) 167 self.set_attribute("text:name", name)
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
173class UserFieldGet(ElementTyped): 174 _tag = "text:user-field-get" 175 _properties = ( 176 PropDef("name", "text:name"), 177 PropDef("style", "style:data-style-name"), 178 ) 179 180 def __init__( 181 self, 182 name: str | None = None, 183 value: Any = None, 184 value_type: str | None = None, 185 text: str | None = None, 186 style: str | None = None, 187 **kwargs: Any, 188 ) -> None: 189 super().__init__(**kwargs) 190 if self._do_init: 191 if name: 192 self.name = name 193 text = self.set_value_and_type( 194 value=value, value_type=value_type, text=text 195 ) 196 self.text = text # type: ignore 197 198 if style: 199 self.style = style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
180 def __init__( 181 self, 182 name: str | None = None, 183 value: Any = None, 184 value_type: str | None = None, 185 text: str | None = None, 186 style: str | None = None, 187 **kwargs: Any, 188 ) -> None: 189 super().__init__(**kwargs) 190 if self._do_init: 191 if name: 192 self.name = name 193 text = self.set_value_and_type( 194 value=value, value_type=value_type, text=text 195 ) 196 self.text = text # type: ignore 197 198 if style: 199 self.style = style
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
376class VarChapter(Element): 377 _tag = "text:chapter" 378 _properties = ( 379 PropDef("display", "text:display"), 380 PropDef("outline_level", "text:outline-level"), 381 ) 382 DISPLAY_VALUE_CHOICE = { # noqa: RUF012 383 "number", 384 "name", 385 "number-and-name", 386 "plain-number", 387 "plain-number-and-name", 388 } 389 390 def __init__( 391 self, 392 display: str | None = "name", 393 outline_level: str | None = None, 394 **kwargs: Any, 395 ) -> None: 396 """display can be: 'number', 'name', 'number-and-name', 'plain-number' or 397 'plain-number-and-name' 398 """ 399 super().__init__(**kwargs) 400 if self._do_init: 401 if display not in VarChapter.DISPLAY_VALUE_CHOICE: 402 raise ValueError("unknown display value: %s" % display) 403 self.display = display 404 if outline_level is not None: 405 self.outline_level = outline_level
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
390 def __init__( 391 self, 392 display: str | None = "name", 393 outline_level: str | None = None, 394 **kwargs: Any, 395 ) -> None: 396 """display can be: 'number', 'name', 'number-and-name', 'plain-number' or 397 'plain-number-and-name' 398 """ 399 super().__init__(**kwargs) 400 if self._do_init: 401 if display not in VarChapter.DISPLAY_VALUE_CHOICE: 402 raise ValueError("unknown display value: %s" % display) 403 self.display = display 404 if outline_level is not None: 405 self.outline_level = outline_level
display can be: 'number', 'name', 'number-and-name', 'plain-number' or 'plain-number-and-name'
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
456class VarCreationDate(Element): 457 _tag = "text:creation-date" 458 _properties = ( 459 PropDef("fixed", "text:fixed"), 460 PropDef("data_style", "style:data-style-name"), 461 ) 462 463 def __init__( 464 self, 465 fixed: bool = False, 466 data_style: str | None = None, 467 **kwargs: Any, 468 ) -> None: 469 super().__init__(**kwargs) 470 if self._do_init: 471 if fixed: 472 self.fixed = True 473 if data_style: 474 self.data_style = data_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
480class VarCreationTime(Element): 481 _tag = "text:creation-time" 482 _properties = ( 483 PropDef("fixed", "text:fixed"), 484 PropDef("data_style", "style:data-style-name"), 485 ) 486 487 def __init__( 488 self, 489 fixed: bool = False, 490 data_style: str | None = None, 491 **kwargs: Any, 492 ) -> None: 493 super().__init__(**kwargs) 494 if self._do_init: 495 if fixed: 496 self.fixed = True 497 if data_style: 498 self.data_style = data_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
305class VarDate(Element): 306 _tag = "text:date" 307 _properties = ( 308 PropDef("date", "text:date-value"), 309 PropDef("fixed", "text:fixed"), 310 PropDef("data_style", "style:data-style-name"), 311 PropDef("date_adjust", "text:date-adjust"), 312 ) 313 314 def __init__( 315 self, 316 date: datetime | None = None, 317 fixed: bool = False, 318 data_style: str | None = None, 319 text: str | None = None, 320 date_adjust: timedelta | None = None, 321 **kwargs: Any, 322 ) -> None: 323 super().__init__(**kwargs) 324 if self._do_init: 325 if date: 326 self.date = DateTime.encode(date) 327 if fixed: 328 self.fixed = True 329 if data_style is not None: 330 self.data_style = data_style 331 if text is None and date is not None: 332 text = Date.encode(date) 333 self.text = text # type: ignore 334 if date_adjust is not None: 335 self.date_adjust = Duration.encode(date_adjust)
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
314 def __init__( 315 self, 316 date: datetime | None = None, 317 fixed: bool = False, 318 data_style: str | None = None, 319 text: str | None = None, 320 date_adjust: timedelta | None = None, 321 **kwargs: Any, 322 ) -> None: 323 super().__init__(**kwargs) 324 if self._do_init: 325 if date: 326 self.date = DateTime.encode(date) 327 if fixed: 328 self.fixed = True 329 if data_style is not None: 330 self.data_style = data_style 331 if text is None and date is not None: 332 text = Date.encode(date) 333 self.text = text # type: ignore 334 if date_adjust is not None: 335 self.date_adjust = Duration.encode(date_adjust)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
40class VarDecl(Element): 41 _tag = "text:variable-decl" 42 _properties = ( 43 PropDef("name", "text:name"), 44 PropDef("value_type", "office:value-type"), 45 ) 46 47 def __init__( 48 self, 49 name: str | None = None, 50 value_type: str | None = None, 51 **kwargs: Any, 52 ) -> None: 53 super().__init__(**kwargs) 54 if self._do_init: 55 if name: 56 self.name = name 57 if value_type: 58 self.value_type = value_type
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
411class VarFileName(Element): 412 _tag = "text:file-name" 413 _properties = ( 414 PropDef("display", "text:display"), 415 PropDef("fixed", "text:fixed"), 416 ) 417 DISPLAY_VALUE_CHOICE = { # noqa: RUF012 418 "full", 419 "path", 420 "name", 421 "name-and-extension", 422 } 423 424 def __init__( 425 self, 426 display: str | None = "full", 427 fixed: bool = False, 428 **kwargs: Any, 429 ) -> None: 430 """display can be: 'full', 'path', 'name' or 'name-and-extension'""" 431 super().__init__(**kwargs) 432 if self._do_init: 433 if display not in VarFileName.DISPLAY_VALUE_CHOICE: 434 raise ValueError("unknown display value: %s" % display) 435 self.display = display 436 if fixed: 437 self.fixed = True
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
424 def __init__( 425 self, 426 display: str | None = "full", 427 fixed: bool = False, 428 **kwargs: Any, 429 ) -> None: 430 """display can be: 'full', 'path', 'name' or 'name-and-extension'""" 431 super().__init__(**kwargs) 432 if self._do_init: 433 if display not in VarFileName.DISPLAY_VALUE_CHOICE: 434 raise ValueError("unknown display value: %s" % display) 435 self.display = display 436 if fixed: 437 self.fixed = True
display can be: 'full', 'path', 'name' or 'name-and-extension'
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
111class VarGet(ElementTyped): 112 _tag = "text:variable-get" 113 _properties = ( 114 PropDef("name", "text:name"), 115 PropDef("style", "style:data-style-name"), 116 ) 117 118 def __init__( 119 self, 120 name: str | None = None, 121 value: Any = None, 122 value_type: str | None = None, 123 text: str | None = None, 124 style: str | None = None, 125 **kwargs: Any, 126 ) -> None: 127 super().__init__(**kwargs) 128 if self._do_init: 129 if name: 130 self.name = name 131 if style: 132 self.style = style 133 text = self.set_value_and_type( 134 value=value, value_type=value_type, text=text 135 ) 136 self.text = text # type: ignore
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
118 def __init__( 119 self, 120 name: str | None = None, 121 value: Any = None, 122 value_type: str | None = None, 123 text: str | None = None, 124 style: str | None = None, 125 **kwargs: Any, 126 ) -> None: 127 super().__init__(**kwargs) 128 if self._do_init: 129 if name: 130 self.name = name 131 if style: 132 self.style = style 133 text = self.set_value_and_type( 134 value=value, value_type=value_type, text=text 135 ) 136 self.text = text # type: ignore
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
443class VarInitialCreator(Element): 444 _tag = "text:initial-creator" 445 _properties = (PropDef("fixed", "text:fixed"),) 446 447 def __init__(self, fixed: bool = False, **kwargs: Any) -> None: 448 super().__init__(**kwargs) 449 if self._do_init and fixed: 450 self.fixed = True
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
Inherited Members
- Element
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
270class VarPageNumber(Element): 271 """ 272 select_page -- string in ('previous', 'current', 'next') 273 274 page_adjust -- int (to add or subtract to the page number) 275 """ 276 277 _tag = "text:page-number" 278 _properties = ( 279 PropDef("select_page", "text:select-page"), 280 PropDef("page_adjust", "text:page-adjust"), 281 ) 282 283 def __init__( 284 self, 285 select_page: str | None = None, 286 page_adjust: str | None = None, 287 **kwargs: Any, 288 ) -> None: 289 super().__init__(**kwargs) 290 if self._do_init: 291 if select_page is None: 292 select_page = "current" 293 self.select_page = select_page 294 if page_adjust is not None: 295 self.page_adjust = page_adjust
select_page -- string in ('previous', 'current', 'next')
page_adjust -- int (to add or subtract to the page number)
283 def __init__( 284 self, 285 select_page: str | None = None, 286 page_adjust: str | None = None, 287 **kwargs: Any, 288 ) -> None: 289 super().__init__(**kwargs) 290 if self._do_init: 291 if select_page is None: 292 select_page = "current" 293 self.select_page = select_page 294 if page_adjust is not None: 295 self.page_adjust = page_adjust
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
64class VarSet(ElementTyped): 65 _tag = "text:variable-set" 66 _properties = ( 67 PropDef("name", "text:name"), 68 PropDef("style", "style:data-style-name"), 69 PropDef("display", "text:display"), 70 ) 71 72 def __init__( 73 self, 74 name: str | None = None, 75 value: Any = None, 76 value_type: str | None = None, 77 display: str | bool = False, 78 text: str | None = None, 79 style: str | None = None, 80 **kwargs: Any, 81 ) -> None: 82 super().__init__(**kwargs) 83 if self._do_init: 84 if name: 85 self.name = name 86 if style: 87 self.style = style 88 text = self.set_value_and_type( 89 value=value, value_type=value_type, text=text 90 ) 91 if not display: 92 self.display = "none" 93 else: 94 self.text = text # type: ignore 95 96 def set_value(self, value: Any) -> None: 97 name = self.get_attribute("text:name") 98 display = self.get_attribute("text:display") 99 self.clear() 100 text = self.set_value_and_type(value=value) 101 self.set_attribute("text:name", name) 102 if display is not None: 103 self.set_attribute("text:display", display) 104 if isinstance(text, str): 105 self.text = text
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
72 def __init__( 73 self, 74 name: str | None = None, 75 value: Any = None, 76 value_type: str | None = None, 77 display: str | bool = False, 78 text: str | None = None, 79 style: str | None = None, 80 **kwargs: Any, 81 ) -> None: 82 super().__init__(**kwargs) 83 if self._do_init: 84 if name: 85 self.name = name 86 if style: 87 self.style = style 88 text = self.set_value_and_type( 89 value=value, value_type=value_type, text=text 90 ) 91 if not display: 92 self.display = "none" 93 else: 94 self.text = text # type: ignore
96 def set_value(self, value: Any) -> None: 97 name = self.get_attribute("text:name") 98 display = self.get_attribute("text:display") 99 self.clear() 100 text = self.set_value_and_type(value=value) 101 self.set_attribute("text:name", name) 102 if display is not None: 103 self.set_attribute("text:display", display) 104 if isinstance(text, str): 105 self.text = text
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
341class VarTime(Element): 342 _tag = "text:time" 343 _properties = ( 344 PropDef("time", "text:time-value"), 345 PropDef("fixed", "text:fixed"), 346 PropDef("data_style", "style:data-style-name"), 347 PropDef("time_adjust", "text:time-adjust"), 348 ) 349 350 def __init__( 351 self, 352 time: datetime, 353 fixed: bool = False, 354 data_style: str | None = None, 355 text: str | None = None, 356 time_adjust: timedelta | None = None, 357 **kwargs: Any, 358 ) -> None: 359 super().__init__(**kwargs) 360 if self._do_init: 361 self.time = DateTime.encode(time) 362 if fixed: 363 self.fixed = True 364 if data_style is not None: 365 self.data_style = data_style 366 if text is None: 367 text = time.strftime("%H:%M:%S") 368 self.text = text 369 if time_adjust is not None: 370 self.date_adjust = Duration.encode(time_adjust)
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
350 def __init__( 351 self, 352 time: datetime, 353 fixed: bool = False, 354 data_style: str | None = None, 355 text: str | None = None, 356 time_adjust: timedelta | None = None, 357 **kwargs: Any, 358 ) -> None: 359 super().__init__(**kwargs) 360 if self._do_init: 361 self.time = DateTime.encode(time) 362 if fixed: 363 self.fixed = True 364 if data_style is not None: 365 self.data_style = data_style 366 if text is None: 367 text = time.strftime("%H:%M:%S") 368 self.text = text 369 if time_adjust is not None: 370 self.date_adjust = Duration.encode(time_adjust)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
Super class of all ODF classes.
Representation of an XML element. Abstraction of the XML library behind.
396 def getter(self: Element) -> str | bool | None: 397 try: 398 if family and self.family != family: # type: ignore 399 return None 400 except AttributeError: 401 return None 402 value = self.__element.get(name) 403 if value is None: 404 return None 405 elif value in ("true", "false"): 406 return Boolean.decode(value) 407 return str(value)
Inherited Members
- Element
- from_tag
- from_tag_for_clone
- make_etree_element
- tag
- elements_repeated_sequence
- get_elements
- get_element
- attributes
- get_attribute
- get_attribute_integer
- get_attribute_string
- set_attribute
- set_style_attribute
- del_attribute
- text
- text_recursive
- tail
- search
- match
- replace
- root
- parent
- is_bound
- children
- index
- text_content
- is_empty
- get_between
- insert
- extend
- append
- delete
- replace_element
- strip_elements
- xpath
- clear
- clone
- serialize
- document_body
- get_formatted_text
- get_styled_elements
- dc_creator
- dc_date
- svg_title
- svg_description
- get_sections
- get_section
- get_paragraphs
- get_paragraph
- get_spans
- get_span
- get_headers
- get_header
- get_lists
- get_list
- get_frames
- get_frame
- get_images
- get_image
- get_tables
- get_table
- get_named_ranges
- get_named_range
- append_named_range
- delete_named_range
- get_notes
- get_note
- get_annotations
- get_annotation
- get_annotation_ends
- get_annotation_end
- get_office_names
- get_variable_decls
- get_variable_decl_list
- get_variable_decl
- get_variable_sets
- get_variable_set
- get_variable_set_value
- get_user_field_decls
- get_user_field_decl_list
- get_user_field_decl
- get_user_field_value
- get_user_defined_list
- get_user_defined
- get_user_defined_value
- get_draw_pages
- get_draw_page
- get_links
- get_link
- get_bookmarks
- get_bookmark
- get_bookmark_starts
- get_bookmark_start
- get_bookmark_ends
- get_bookmark_end
- get_reference_marks_single
- get_reference_mark_single
- get_reference_mark_starts
- get_reference_mark_start
- get_reference_mark_ends
- get_reference_mark_end
- get_reference_marks
- get_reference_mark
- get_references
- get_draw_groups
- get_draw_group
- get_draw_lines
- get_draw_line
- get_draw_rectangles
- get_draw_rectangle
- get_draw_ellipses
- get_draw_ellipse
- get_draw_connectors
- get_draw_connector
- get_orphan_draw_connectors
- get_tracked_changes
- get_changes_ids
- get_text_change_deletions
- get_text_change_deletion
- get_text_change_starts
- get_text_change_start
- get_text_change_ends
- get_text_change_end
- get_text_changes
- get_text_change
- get_tocs
- get_toc
- get_styles
- get_style
37class XmlPart: 38 """Representation of an XML part. 39 40 Abstraction of the XML library behind. 41 """ 42 43 def __init__(self, part_name: str, container: Container) -> None: 44 self.part_name = part_name 45 self.container = container 46 47 # Internal state 48 self.__tree: _ElementTree | None = None 49 self.__root: Element | None = None 50 51 def _get_tree(self) -> _ElementTree: 52 if self.__tree is None: 53 part = self.container.get_part(self.part_name) 54 self.__tree = parse(BytesIO(part)) # type: ignore 55 return self.__tree 56 57 def __repr__(self) -> str: 58 return f"<{self.__class__.__name__} part_name={self.part_name}>" 59 60 # Public API 61 62 @property 63 def root(self) -> Element: 64 if self.__root is None: 65 tree = self._get_tree() 66 self.__root = Element.from_tag(tree.getroot()) 67 return self.__root 68 69 def get_elements(self, xpath_query: str) -> list[Element | Text]: 70 root = self.root 71 return root.xpath(xpath_query) 72 73 def get_element(self, xpath_query: str) -> Any: 74 result = self.get_elements(xpath_query) 75 if not result: 76 return None 77 return result[0] 78 79 def delete_element(self, child: Element) -> None: 80 child.delete() 81 82 def xpath(self, xpath_query: str) -> list[Element | Text]: 83 """Apply XPath query to the XML part. Return list of Element or 84 Text instances translated from the nodes found. 85 """ 86 root = self.root 87 return root.xpath(xpath_query) 88 89 @property 90 def clone(self) -> XmlPart: 91 clone = object.__new__(self.__class__) 92 for name in self.__dict__: 93 if name == "container": 94 setattr(clone, name, self.container.clone) 95 elif name in ("_XmlPart__tree",): 96 setattr(clone, name, None) 97 else: 98 value = getattr(self, name) 99 value = deepcopy(value) 100 setattr(clone, name, value) 101 return clone 102 103 def serialize(self, pretty: bool = False) -> bytes: 104 tree = self._get_tree() 105 # Lxml declaration is too exotic to me 106 data = [b'<?xml version="1.0" encoding="UTF-8"?>'] 107 bytes_tree = tostring(tree, pretty_print=pretty, encoding="utf-8") 108 # Lxml with pretty_print is adding a empty line 109 if pretty: 110 bytes_tree = bytes_tree.strip() 111 data.append(bytes_tree) 112 return b"\n".join(data)
Representation of an XML part.
Abstraction of the XML library behind.
82 def xpath(self, xpath_query: str) -> list[Element | Text]: 83 """Apply XPath query to the XML part. Return list of Element or 84 Text instances translated from the nodes found. 85 """ 86 root = self.root 87 return root.xpath(xpath_query)
Apply XPath query to the XML part. Return list of Element or Text instances translated from the nodes found.
89 @property 90 def clone(self) -> XmlPart: 91 clone = object.__new__(self.__class__) 92 for name in self.__dict__: 93 if name == "container": 94 setattr(clone, name, self.container.clone) 95 elif name in ("_XmlPart__tree",): 96 setattr(clone, name, None) 97 else: 98 value = getattr(self, name) 99 value = deepcopy(value) 100 setattr(clone, name, value) 101 return clone
103 def serialize(self, pretty: bool = False) -> bytes: 104 tree = self._get_tree() 105 # Lxml declaration is too exotic to me 106 data = [b'<?xml version="1.0" encoding="UTF-8"?>'] 107 bytes_tree = tostring(tree, pretty_print=pretty, encoding="utf-8") 108 # Lxml with pretty_print is adding a empty line 109 if pretty: 110 bytes_tree = bytes_tree.strip() 111 data.append(bytes_tree) 112 return b"\n".join(data)
187def create_table_cell_style( 188 border: str | None = None, 189 border_top: str | None = None, 190 border_bottom: str | None = None, 191 border_left: str | None = None, 192 border_right: str | None = None, 193 padding: str | None = None, 194 padding_top: str | None = None, 195 padding_bottom: str | None = None, 196 padding_left: str | None = None, 197 padding_right: str | None = None, 198 background_color: str | tuple | None = None, 199 shadow: str | None = None, 200 color: str | tuple | None = None, 201) -> Style: 202 """Return a cell style. 203 204 The borders arguments must be some style attribute strings or None, see the 205 method 'make_table_cell_border_string' to generate them. 206 If the 'border' argument as the value 'default', the default style 207 "0.06pt solid #000000" is used for the 4 borders. 208 If any value is used for border, it is used for the 4 borders, else any of 209 the 4 borders can be specified by it's own string. If all the border, 210 border_top, border_bottom, ... arguments are None, an empty border is used 211 (ODF value is fo:border="none"). 212 213 Padding arguments are string specifying a length (e.g. "0.5mm")". If 214 'padding' is provided, it is used for the 4 sides, else any of 215 the 4 sides padding can be specified by it's own string. Default padding is 216 no padding. 217 218 Arguments: 219 220 border -- str, style string for borders on four sides 221 222 border_top -- str, style string for top if no 'border' argument 223 224 border_bottom -- str, style string for bottom if no 'border' argument 225 226 border_left -- str, style string for left if no 'border' argument 227 228 border_right -- str, style string for right if no 'border' argument 229 230 padding -- str, style string for padding on four sides 231 232 padding_top -- str, style string for top if no 'padding' argument 233 234 padding_bottom -- str, style string for bottom if no 'padding' argument 235 236 padding_left -- str, style string for left if no 'padding' argument 237 238 padding_right -- str, style string for right if no 'padding' argument 239 240 background_color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345' 241 242 shadow -- str, e.g. "#808080 0.176cm 0.176cm" 243 244 color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345' 245 246 Return : Style 247 """ 248 if border == "default": 249 border = make_table_cell_border_string() # default border 250 if border is not None: 251 # use the border value for 4 sides. 252 border_bottom = border_top = border_left = border_right = None 253 if ( 254 border is None 255 and border_bottom is None 256 and border_top is None 257 and border_left is None 258 and border_right is None 259 ): 260 border = "none" 261 if padding is not None: 262 # use the padding value for 4 sides. 263 padding_bottom = padding_top = padding_left = padding_right = None 264 cell_style = Style( 265 "table-cell", 266 area="table-cell", 267 border=border, 268 border_top=border_top, 269 border_bottom=border_bottom, 270 border_left=border_left, 271 border_right=border_right, 272 padding=padding, 273 padding_top=padding_top, 274 padding_bottom=padding_bottom, 275 padding_left=padding_left, 276 padding_right=padding_right, 277 background_color=background_color, 278 shadow=shadow, 279 ) 280 if color: 281 cell_style.set_properties(area="text", color=color) 282 return cell_style
Return a cell style.
The borders arguments must be some style attribute strings or None, see the method 'make_table_cell_border_string' to generate them. If the 'border' argument as the value 'default', the default style "0.06pt solid #000000" is used for the 4 borders. If any value is used for border, it is used for the 4 borders, else any of the 4 borders can be specified by it's own string. If all the border, border_top, border_bottom, ... arguments are None, an empty border is used (ODF value is fo:border="none").
Padding arguments are string specifying a length (e.g. "0.5mm")". If 'padding' is provided, it is used for the 4 sides, else any of the 4 sides padding can be specified by it's own string. Default padding is no padding.
Arguments:
border -- str, style string for borders on four sides
border_top -- str, style string for top if no 'border' argument
border_bottom -- str, style string for bottom if no 'border' argument
border_left -- str, style string for left if no 'border' argument
border_right -- str, style string for right if no 'border' argument
padding -- str, style string for padding on four sides
padding_top -- str, style string for top if no 'padding' argument
padding_bottom -- str, style string for bottom if no 'padding' argument
padding_left -- str, style string for left if no 'padding' argument
padding_right -- str, style string for right if no 'padding' argument
background_color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345'
shadow -- str, e.g. "#808080 0.176cm 0.176cm"
color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345'
Return : Style
1108def default_currency_style() -> Element: 1109 return Element.from_tag( 1110 """<number:currency-style style:name="lpod-default-currency-style"> 1111 <number:text>-</number:text> 1112 <number:number number:decimal-places="2" 1113 number:min-integer-digits="1" 1114 number:grouping="true"/> 1115 <number:text> </number:text> 1116 <number:currency-symbol 1117 number:language="fr" 1118 number:country="FR">€</number:currency-symbol> 1119 </number:currency-style>""" 1120 )
1087def default_date_style() -> Element: 1088 return Element.from_tag( 1089 """ 1090 <number:date-style style:name="lpod-default-date-style"> 1091 <number:year number:style="long"/> 1092 <number:text>-</number:text> 1093 <number:month number:style="long"/> 1094 <number:text>-</number:text> 1095 <number:day number:style="long"/> 1096 </number:date-style>""" 1097 )
42def default_frame_position_style( 43 name: str = "FramePosition", 44 horizontal_pos: str = "from-left", 45 vertical_pos: str = "from-top", 46 horizontal_rel: str = "paragraph", 47 vertical_rel: str = "paragraph", 48) -> Style: 49 """Helper style for positioning frames in desktop applications that need 50 it. 51 52 Default arguments should be enough. 53 54 Use the returned Style as the frame style or build a new graphic style 55 with this style as the parent. 56 """ 57 return Style( 58 family="graphic", 59 name=name, 60 horizontal_pos=horizontal_pos, 61 horizontal_rel=horizontal_rel, 62 vertical_pos=vertical_pos, 63 vertical_rel=vertical_rel, 64 )
Helper style for positioning frames in desktop applications that need it.
Default arguments should be enough.
Use the returned Style as the frame style or build a new graphic style with this style as the parent.
1064def default_percentage_style() -> Element: 1065 return Element.from_tag( 1066 """<number:percentage-style 1067 style:name="lpod-default-percentage-style"> 1068 <number:number number:decimal-places="2" 1069 number:min-integer-digits="1"/> 1070 <number:text>%</number:text> 1071 </number:percentage-style>""" 1072 )
1075def default_time_style() -> Element: 1076 return Element.from_tag( 1077 """<number:time-style style:name="lpod-default-time-style"> 1078 <number:hours number:style="long"/> 1079 <number:text>:</number:text> 1080 <number:minutes number:style="long"/> 1081 <number:text>:</number:text> 1082 <number:seconds number:style="long"/> 1083 </number:time-style>""" 1084 )
150def default_toc_level_style(level: int) -> Style: 151 """Generate an automatic default style for the given TOC level.""" 152 tab_stop = TabStopStyle(style_type="right", leader_style="dotted", leader_text=".") 153 position = 17.5 - (0.5 * level) 154 tab_stop.style_position = f"{position}cm" 155 tab_stops = Element.from_tag("style:tab-stops") 156 tab_stops.append(tab_stop) 157 properties = Element.from_tag("style:paragraph-properties") 158 properties.append(tab_stops) 159 toc_style_level = Style( 160 family="paragraph", 161 name=_toc_entry_style_name(level), 162 parent=f"Contents_20_{level}", 163 ) 164 toc_style_level.append(properties) 165 return toc_style_level
Generate an automatic default style for the given TOC level.
26def hex2rgb(color: str) -> tuple[int, int, int]: 27 """Turns a "#RRGGBB" hexadecimal color representation into a (R, G, B) 28 tuple. 29 30 Arguments: 31 32 color -- str 33 34 Return: tuple 35 """ 36 code = color[1:] 37 if not (len(color) == 7 and color[0] == "#" and code.isalnum()): 38 raise ValueError(f'"{color}" is not a valid color') 39 red = int(code[:2], 16) 40 green = int(code[2:4], 16) 41 blue = int(code[4:6], 16) 42 return (red, green, blue)
Turns a "#RRGGBB" hexadecimal color representation into a (R, G, B) tuple.
Arguments:
color -- str
Return: tuple
81def hexa_color(color: str | tuple[int, int, int] | None = None) -> str | None: 82 """Convert a color definition of type tuple or string to hexadecimal 83 representation. 84 85 Empty string is converted to black. 86 None is converted to None. 87 88 Arguments: 89 90 color -- str or tuple or None 91 92 Return: str or None 93 """ 94 if color is None: 95 return None 96 if isinstance(color, tuple): 97 return rgb2hex(color) 98 if not isinstance(color, str): 99 raise TypeError(f'Invalid color argument "{color!r}"') 100 color = color.strip() 101 if not color: 102 return "#000000" 103 if color.startswith("#"): 104 return color 105 return rgb2hex(color)
Convert a color definition of type tuple or string to hexadecimal representation.
Empty string is converted to black. None is converted to None.
Arguments:
color -- str or tuple or None
Return: str or None
167def make_table_cell_border_string( 168 thick: str | float | int | None = None, 169 line: str | None = None, 170 color: str | tuple | None = None, 171) -> str: 172 """Returns a string for style:table-cell-properties fo:border, 173 with default : "0.06pt solid #000000" 174 175 thick -- str or float or int 176 line -- str 177 color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345' 178 179 Returns : str 180 """ 181 thick_string = _make_thick_string(thick) 182 line_string = _make_line_string(line) 183 color_string = hexa_color(color) or "#000000" 184 return " ".join((thick_string, line_string, color_string))
Returns a string for style:table-cell-properties fo:border, with default : "0.06pt solid #000000"
thick -- str or float or int
line -- str
color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345'
Returns : str
45def rgb2hex(color: str | tuple[int, int, int]) -> str: 46 """Turns a color name or a (R, G, B) color tuple into a "#RRGGBB" 47 hexadecimal representation. 48 49 Arguments: 50 51 color -- str or tuple 52 53 Return: str 54 55 Examples:: 56 57 >>> rgb2hex('yellow') 58 '#FFFF00' 59 >>> rgb2hex((238, 130, 238)) 60 '#EE82EE' 61 """ 62 if isinstance(color, str): 63 try: 64 code = CSS3_COLORMAP[color.lower()] 65 except KeyError as e: 66 raise KeyError(f'Color "{color}" is unknown in CSS color list') from e 67 elif isinstance(color, tuple): 68 if len(color) != 3: 69 raise ValueError("Color must be a 3-tuple") 70 code = color 71 else: 72 raise TypeError(f'Invalid color "{color}"') 73 for channel in code: 74 if not 0 <= channel <= 255: 75 raise ValueError( 76 f'Invalid color "{color}", channel must be between 0 and 255' 77 ) 78 return f"#{code[0]:02X}{code[1]:02X}{code[2]:02X}"
Turns a color name or a (R, G, B) color tuple into a "#RRGGBB" hexadecimal representation.
Arguments:
color -- str or tuple
Return: str
Examples::
>>> rgb2hex('yellow')
'#FFFF00'
>>> rgb2hex((238, 130, 238))
'#EE82EE'